Send email from Django

Five steps: install the SDK, create a test key, send to the sandbox from a view, read the log, then verify a domain from a management command so you can send to anyone.

1. Install

The Python SDK is on PyPI as avelto. Install it in your project's environment.

shell
pip install avelto

2. Create an API key

Sign in, open API keys in the dashboard and create a test key. It starts with av_test_. Export it, and read it into settings so nothing else touches the environment directly:

shell
export AVELTO_API_KEY=av_test_...
Python
# settings.pyimport osAVELTO_API_KEY = os.environ["AVELTO_API_KEY"]

3. Send your first email

One client for the process, in a module of its own. It holds a connection pool and retries for you.

Python
# myapp/avelto.pyfrom avelto import Aveltofrom django.conf import settings# One client for the process: it holds a connection pool and retries for you.client = Avelto(settings.AVELTO_API_KEY)

The view sends and returns the API's answer, or the error as the same envelope the API uses. from_ is the sender, because from is a Python keyword.

Python
# myapp/views.pyfrom avelto import AveltoErrorfrom django.http import JsonResponsefrom django.views.decorators.csrf import csrf_exemptfrom django.views.decorators.http import require_POSTfrom .avelto import client@csrf_exempt  # so you can try it with curl; drop this behind a form or auth@require_POSTdef send(request):    try:        email = client.emails.send(            from_="you@sandbox.avelto.dev",            to="delivered@sandbox.avelto.dev",            subject="Hello from Avelto",            text="It works.",        )    except AveltoError as e:        return JsonResponse({"error": {"code": e.code, "message": e.message}}, status=e.status or 502)    return JsonResponse(email, status=201)  # {"id": "9c1f4a52-..."}
Python
# urls.pyfrom django.urls import pathfrom myapp import viewsurlpatterns = [    path("send", views.send),]

Run python manage.py runserver and call the view:

shell
curl -X POST http://localhost:8000/send

The API answers 201 Created with the email id, and the view returns the same:

HTTP
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }

4. Check the log

A second view fetches the email by id, with a second route for it. status moves from queued to sent to delivered, and events records each step: email.queued, email.sent, email.delivered.

Python
# myapp/views.py (add to the file from step 3)def status(request, email_id):    email = client.emails.get(str(email_id))    return JsonResponse({        "status": email["status"],  # "queued", then "sent", then "delivered"        "events": [e["type"] for e in email["events"]],    })
Python
# urls.py (add the second route)urlpatterns = [    path("send", views.send),    path("emails/<uuid:email_id>", views.status),]
shell
curl http://localhost:8000/emails/9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10

5. Verify a domain

Adding a domain is a one-off task, so it fits a management command. Publish the DNS records it prints (an ownership TXT, an SPF TXT and a DMARC TXT, then three DKIM CNAMEs once the ownership record is found) at your DNS provider; the command polls domains.get, which re-checks DNS on every call, until status is verified. Use a subdomain such as mail.acme.com.

Python
# myapp/management/commands/verify_domain.pyimport timefrom django.core.management.base import BaseCommandfrom myapp.avelto import clientclass Command(BaseCommand):    help = "Add a sending domain and wait until it is verified"    def add_arguments(self, parser):        parser.add_argument("name")    def handle(self, *args, **options):        domain = client.domains.create(options["name"])        self.show(domain)  # the ownership record first, with SPF and DMARC        # Publish the records, then poll. GET re-checks DNS on every call.        # Once the ownership record is found, the DKIM records appear: publish those too.        while domain["status"] == "pending":            time.sleep(30)            latest = client.domains.get(domain["id"])            if latest["ownership_verified"] and not domain["ownership_verified"]:                self.show(latest)            domain = latest        self.stdout.write(domain["status"])  # "verified" or "failed"    def show(self, domain):        for rec in domain["dns_records"]:            self.stdout.write(                f"{rec['type']}\t{rec['name']}\t{rec['value']}\t({rec['purpose']})"            )
shell
python manage.py verify_domain mail.acme.com

Once the domain is verified, switch AVELTO_API_KEY to a live key (av_live_) and change from_ in the view to an address on it, such as hello@mail.acme.com. Nothing else changes.

Next

  • Python SDK: the whole client, sync and asyncio, with retries, batch send, templates and webhooks.
  • Send email: every field, attachments, tags, scheduling and idempotency.
  • Webhooks: get events pushed to your app, with verify_webhook_signature in a Django view.
  • Test mode: test keys, the sandbox sender and the simulator addresses.