Django

A custom email backend, so send_mail and every EmailMessage in the project goes through emails.sh.

Django routes all mail through EMAIL_BACKEND, including password resets and admin errors. Write one backend and everything in the project, including code you have not read, sends through emails.sh.

The key

.env
# .env, loaded with django-environ or python-dotenv.
# The key comes from https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_here

The backend

acme/email_backend.py
# acme/email_backend.py
import json
import urllib.error
import urllib.request

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

ENDPOINT = "https://emails.sh/v1/emails"


class EmailsshBackend(BaseEmailBackend):
    """Send Django's EmailMessage objects through the emails.sh API."""

    def send_messages(self, email_messages):
        sent = 0
        for message in email_messages:
            payload = {
                "from": message.from_email or settings.DEFAULT_FROM_EMAIL,
                "to": list(message.to),
                "subject": message.subject,
                "text": message.body,
            }
            if message.cc:
                payload["cc"] = list(message.cc)
            if message.bcc:
                payload["bcc"] = list(message.bcc)
            if message.reply_to:
                payload["reply_to"] = message.reply_to[0]

            # EmailMultiAlternatives puts the HTML part here.
            for content, mimetype in getattr(message, "alternatives", []):
                if mimetype == "text/html":
                    payload["html"] = content

            request = urllib.request.Request(
                ENDPOINT,
                data=json.dumps(payload).encode(),
                headers={
                    "Authorization": f"Bearer {settings.EMAILSSH_API_KEY}",
                    "Content-Type": "application/json",
                },
                method="POST",
            )

            try:
                with urllib.request.urlopen(request, timeout=10):
                    sent += 1
            except urllib.error.HTTPError as err:
                refusal = json.loads(err.read())["error"]
                if not self.fail_silently:
                    raise RuntimeError(
                        f"emails.sh refused the send: {refusal['code']}: "
                        f"{refusal['message']} {refusal.get('next', '')}"
                    ) from err

        return sent
settings.py
# settings.py
import os

EMAIL_BACKEND = "acme.email_backend.EmailsshBackend"
EMAILSSH_API_KEY = os.environ["EMAILSSH_API_KEY"]
DEFAULT_FROM_EMAIL = "Acme <hello@acme.com>"

Use it

acme/emails.py
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string


def send_welcome(user):
    html = render_to_string("email/welcome.html", {"user": user})
    message = EmailMultiAlternatives(
        subject="Welcome to Acme",
        body="Confirm your address to finish signing up.",
        to=[user.email],
    )
    message.attach_alternative(html, "text/html")
    message.send()