In Django you can send transactional email with an HTTPS call from a view, signal handler, or Celery task — keep the API key in settings/env, never in templates. For Notify, POST https://notify.cx/api/email/send with header x-api-key and JSON to, from, subject, message. You do not need Django’s SMTP email backend for this path.
Let's take a look at How to send emails from a Django app for password resets, receipts, invites, and other product mail in 2026.
Django’s built-in email vs an email API
Django ships django.core.mail and backends that speak SMTP. That still works. It is also where a lot of projects stall:
EMAIL_HOST/ port / TLS settings differ per environment- Serverless or container deploys block or throttle outbound SMTP
- Bounce handling and provider logs live outside Django
- You end up maintaining both Django settings and a third-party console
An email API keeps Django as the app and the provider as the delivery pipe. Same idea as calling Stripe: HTTPS + secret key.
| Approach | When it fits |
|---|---|
| Django SMTP backend | Legacy apps already wired; internal relays you must keep |
Email API (requests / httpx) | New transactional mail; PaaS/serverless; you want provider logs/webhooks |
| SES SDK in Django | You already run AWS deeply and will own IAM + SNS |
Provider choice (honest)
- Notify — minimal transactional API; Free 1k / Pro $10·10k / Scale $50·100k; one HTTP call
- Resend — strong if you want their Node-centric DX elsewhere in a polyglot shop
- Postmark — when deliverability reputation is the buying criterion
- Amazon SES — lowest unit cost at scale if you accept AWS setup and possible production-access waits on new accounts
Best for Notify: SaaS and product teams sending transactional mail from Django without ESP sprawl. Not for: newsletters, marketing automation, or visual template builders.
Prerequisites
- Django 4.2+ (examples work on 5.x)
- Python 3.10+
NOTIFY_API_KEYin the environment- A verified domain for production
from requestsorhttpx(stdliburllibalso works)
pip install requests
export NOTIFY_API_KEY=your_api_key_here
Settings
# settings.py
import os
NOTIFY_API_KEY = os.environ["NOTIFY_API_KEY"]
NOTIFY_FROM = os.environ.get("NOTIFY_FROM", "noreply@your-verified-domain.com")
NOTIFY_SEND_URL = "https://notify.cx/api/email/send"
Do not commit the key. Use .env locally and your host’s secret store in production.
Small helper module
# mailer/notify.py
from django.conf import settings
import requests
class NotifyError(Exception):
pass
def send_email(*, to: str, subject: str, message: str, from_addr: str | None = None) -> dict:
response = requests.post(
settings.NOTIFY_SEND_URL,
headers={
"Content-Type": "application/json",
"x-api-key": settings.NOTIFY_API_KEY,
},
json={
"from": from_addr or settings.NOTIFY_FROM,
"to": to,
"subject": subject,
"message": message, # plain text or HTML
},
timeout=15,
)
if not response.ok:
raise NotifyError(f"{response.status_code}: {response.text}")
return response.json()
message can be HTML. Build the string in Python, render a Django template to a string, or let an AI draft HTML — Notify delivers either way.
Call it from a view
# accounts/views.py
from django.http import JsonResponse, HttpRequest
from django.views.decorators.http import require_POST
from mailer.notify import send_email, NotifyError
@require_POST
def send_welcome(request: HttpRequest):
email = request.POST.get("email")
if not email:
return JsonResponse({"error": "email required"}, status=400)
try:
send_email(
to=email,
subject="Welcome",
message="<h1>Welcome</h1><p>Thanks for joining.</p>",
)
except NotifyError as exc:
return JsonResponse({"error": str(exc)}, status=502)
return JsonResponse({"ok": True})
Password reset (pattern, not a full auth rewrite)
Keep Django’s token generation (PasswordResetTokenGenerator or your own). Only swap the send step:
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from mailer.notify import send_email
def queue_password_reset(user, reset_base_url: str) -> None:
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
link = f"{reset_base_url}/{uid}/{token}/"
send_email(
to=user.email,
subject="Reset your password",
message=(
"<p>We received a reset request.</p>"
f'<p><a href="{link}">Reset password</a></p>'
"<p>If that was not you, ignore this email.</p>"
),
from_addr="security@your-verified-domain.com",
)
Tokens stay in your database / Django machinery. Notify only sees the HTML you send.
Prefer Celery (or similar) for user-facing waits
Sending inline in a request is fine for low volume. Under load, push to a worker so the HTTP response does not wait on Notify:
# mailer/tasks.py
from celery import shared_task
from mailer.notify import send_email
@shared_task(bind=True, max_retries=3)
def send_email_task(self, to: str, subject: str, message: str) -> None:
try:
send_email(to=to, subject=subject, message=message)
except Exception as exc:
raise self.retry(exc=exc, countdown=20)
Then send_email_task.delay(...) from the view. Same helper; different timing.
Signals (use carefully)
post_save on User for welcome emails works until someone bulk-imports users and you spam them. Prefer explicit calls from the signup path, or gate the signal on created and a clear “signup source” flag.
What about django.core.mail?
You can write a custom email backend that POSTs to Notify. Useful if a lot of code already calls send_mail(). For greenfield transactional flows, a thin send_email() helper is clearer and easier to test with responses / httpx mocks.
Sandbox vs production
Notify accounts start sandbox-oriented until the domain is verified. Use the dashboard guided test for a real inbox moment; the test API path is for non-delivering rehearsal. Production custom from needs DNS (SPF/DKIM) — see domain verification.
FAQ
How do I send emails from a Django app?
Call an HTTPS email API from server-side Python (requests/httpx) with your API key in settings. With Notify: POST https://notify.cx/api/email/send, header x-api-key, body to / from / subject / message.
Do I still need EMAIL_HOST?
Not for the Notify path. Leave Django’s SMTP settings alone (or unused) unless something else in the project still requires them.
Can I use HTML templates?
Yes. render_to_string('emails/welcome.html', context) and pass the result as message.
Is this for newsletters?
No. Transactional product email only. Use a marketing ESP for campaigns.
Related
- Python email API guide
- FastAPI guide (same HTTP contract)
- Quick start
- Pricing
Further Reading
Discover more articles on similar topics across our network
Comments
Loading comments…