Guides / Django password reset emails

How to send Django password reset emails

Short answer: Django's password reset view sends through whatever EMAIL_BACKEND you set. Add a small SendFox email backend, point EMAIL_BACKEND at it, set DEFAULT_FROM_EMAIL to an address on your verified domain, and include django.contrib.auth.urls. Free for 3,000 emails a month.

Get a free API key

This works on Django 4.2 and later and needs the requests package (pip install requests).

Step 1: Get an API key

Sign up free and open Transactional. Your key is created on the first visit. Store it in the SENDFOX_API_KEY environment variable.

Step 2: Add the email backend

One API call per message. It sends the plain-text body and the HTML alternative when there is one, and raises with the API's explanation when something's wrong.

myproject/sendfox.py

import requests
from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend

API_URL = "https://api.sendfox.com/transactional/emails"


class SendFoxEmailBackend(BaseEmailBackend):
    def send_messages(self, email_messages):
        sent = 0
        for message in email_messages:
            if message.attachments:
                raise ValueError("SendFox does not support attachments yet.")

            payload = {
                "from": message.from_email,
                "to": message.to,
                "cc": message.cc,
                "bcc": message.bcc,
                "reply_to": message.reply_to[0] if message.reply_to else None,
                "subject": message.subject,
                "text": message.body,
            }
            for content, mimetype in getattr(message, "alternatives", []):
                if mimetype == "text/html":
                    payload["html"] = content

            response = requests.post(
                API_URL,
                json={key: value for key, value in payload.items() if value},
                headers={"Authorization": f"Bearer {settings.SENDFOX_API_KEY}"},
                timeout=10,
            )
            if response.ok:
                sent += 1
            elif not self.fail_silently:
                # Every error has a stable name and a fix, e.g. "domain_not_verified".
                error = response.json()
                raise RuntimeError(f"{error['name']}: {error['fix']}")
        return sent

Step 3: Point Django at it

settings.py

import os

EMAIL_BACKEND = "myproject.sendfox.SendFoxEmailBackend"
SENDFOX_API_KEY = os.environ["SENDFOX_API_KEY"]
DEFAULT_FROM_EMAIL = "Acme <[email protected]>"

Step 4: Wire up password reset

Django's built-in views handle the whole flow: the form, the signed one-time link, and the "set a new password" page. Give the reset view an HTML template so the email isn't plain text only:

urls.py

from django.contrib.auth import views as auth_views
from django.urls import include, path

urlpatterns = [
    path(
        "accounts/password_reset/",
        auth_views.PasswordResetView.as_view(
            html_email_template_name="registration/password_reset_email.html",
        ),
        name="password_reset",
    ),
    path("accounts/", include("django.contrib.auth.urls")),
]

Put the HTML version of the email in templates/registration/password_reset_email.html. Django passes it protocol, domain, uid and token to build the link, the same as the plain-text template.

Step 5: Test, then verify your domain

python manage.py shell -c "from django.core.mail import send_mail; send_mail('Test', 'It works.', None, ['[email protected]'])"

Send to [email protected] first. Test addresses never reach a mailbox and never count against your quota, and [email protected] and [email protected] fake those outcomes so you can see your error handling and webhook fire. Until your domain is verified, send from [email protected], which delivers to the test addresses and your own account email.

Then add your domain under Account > Domains, paste the DNS records into your DNS provider and click verify. After that, send from any address on it.

Things to know

Full reference: Transactional API docs, or the same docs as Markdown to hand to your coding agent.

FAQ

Why aren't my Django password reset emails sending?

In development Django often uses the console backend, which prints mail instead of sending it. In production you need a real EMAIL_BACKEND and a from address on a domain you have verified. With the backend on this page, a failed send raises an error that names the problem and the fix.

Does this work for send_mail() and EmailMessage too?

Yes. Every email Django sends goes through EMAIL_BACKEND, so send_mail(), EmailMessage, EmailMultiAlternatives and the auth views all use it.

Can I use SMTP instead?

No. SendFox does not offer SMTP, so Django's SMTP backend cannot send through it. The backend on this page uses the HTTPS API.

How much does it cost?

Free for 3,000 emails a month. $15 a month for 50,000.

Send your first email in two minutes.

Free for 3,000 emails a month. $15 a month for 50,000. Already on Resend? Change two environment variables.

Get your free API key

See everything the email API does