heartwood every commit a ring

Replace Exim relay with Python direct-to-MX email backend

52a6a31c by Isaac Bythewood · 24 days ago

modified docker-compose.yml
@@ -1,10 +1,6 @@version: "3"services:  email:    container_name: status_email    image: overshard/exim    restart: unless-stopped  web:    container_name: status_web    build: .
modified pyproject.toml
@@ -4,6 +4,7 @@ version = "0.0.1"requires-python = ">=3.10"dependencies = [    "django",    "dnspython",    "gunicorn",    "requests",    "tzdata",
added status/mailer.py
@@ -0,0 +1,53 @@import loggingimport smtplibfrom concurrent.futures import ThreadPoolExecutorimport dns.resolverfrom django.core.mail.backends.base import BaseEmailBackendlog = logging.getLogger(__name__)_POOL = ThreadPoolExecutor(max_workers=4, thread_name_prefix="mailer")def _deliver(message):    try:        by_domain = {}        for rcpt in message.to + message.cc + message.bcc:            by_domain.setdefault(rcpt.rsplit("@", 1)[1], []).append(rcpt)        payload = message.message().as_bytes()        sender = message.from_email        for domain, rcpts in by_domain.items():            mxs = sorted(                dns.resolver.resolve(domain, "MX"),                key=lambda r: r.preference,            )            for mx in mxs:                host = str(mx.exchange).rstrip(".")                try:                    with smtplib.SMTP(                        host, 25, local_hostname="bythewood.me", timeout=30                    ) as smtp:                        smtp.ehlo()                        try:                            smtp.starttls()                            smtp.ehlo()                        except smtplib.SMTPNotSupportedError:                            pass                        smtp.sendmail(sender, rcpts, payload)                    break                except Exception as exc:                    log.warning("MX %s failed for %s: %s", host, domain, exc)            else:                log.error("all MX hosts failed for %s", domain)    except Exception:        log.exception("mail delivery failed")class DirectMXBackend(BaseEmailBackend):    def send_messages(self, email_messages):        for message in email_messages:            _POOL.submit(_deliver, message)        return len(email_messages)
modified status/settings/production.py
@@ -53,10 +53,11 @@ DATABASES = {# Email# https://docs.djangoproject.com/en/4.0/topics/email/#smtp-backend# https://docs.djangoproject.com/en/4.0/topics/email/#email-backendsEMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"EMAIL_HOST = "email"EMAIL_BACKEND = "status.mailer.DirectMXBackend"DEFAULT_FROM_EMAIL = "noreply@bythewood.me"SERVER_EMAIL = "noreply@bythewood.me"# Media files (Images, Videos)
modified uv.lock
@@ -244,6 +244,15 @@ wheels = [    { url = "https://files.pythonhosted.org/packages/e9/47/3d61d611609764aa71a37f7037b870e7bfb22937366974c4fd46cada7bab/django-6.0.4-py3-none-any.whl", hash = "sha256:14359c809fc16e8f81fd2b59d7d348e4d2d799da6840b10522b6edf7b8afc1da", size = 8368342, upload-time = "2026-04-07T13:55:37.999Z" },][[package]]name = "dnspython"version = "2.8.0"source = { registry = "https://pypi.org/simple" }sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }wheels = [    { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },][[package]]name = "flake8"version = "7.3.0"
@@ -558,6 +567,7 @@ dependencies = [    { name = "beautifulsoup4" },    { name = "django", version = "5.2.13", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },    { name = "django", version = "6.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },    { name = "dnspython" },    { name = "gunicorn" },    { name = "lxml" },    { name = "requests" },
@@ -577,6 +587,7 @@ dev = [requires-dist = [    { name = "beautifulsoup4" },    { name = "django" },    { name = "dnspython" },    { name = "gunicorn" },    { name = "lxml" },    { name = "requests" },