Self-hosted Mastodon via Posthorn
You’re running a self-hosted Mastodon instance. Mastodon sends email for account confirmation, password resets, and notification digests via SMTP. On a cloud host that blocks outbound SMTP — or when you’d rather keep your provider key in one place — Posthorn’s SMTP listener accepts Mastodon’s connection on a private network and forwards over HTTPS to your transactional provider.
At the end: Mastodon’s sidekiq (which sends the mail) talks to posthorn:2525 over a private Docker network with no TLS and no AUTH, and Posthorn forwards via the configured HTTP transport.
The shape
Section titled “The shape”Walkthrough
Section titled “Walkthrough”-
Same private Docker network, no host-port exposure for Posthorn (see the Authentik recipe for the compose skeleton — it’s identical).
-
Write
posthorn.toml.[smtp_listener]listen = ":2525"auth_required = "none"require_tls = falsetrusted_network = true # required for auth "none" on a container bind (#41)allowed_senders = ["*@yourdomain.com"][smtp_listener.transport]type = "resend"[smtp_listener.transport.settings]api_key = "${env.RESEND_API_KEY}" -
Point Mastodon at Posthorn in its
.env.production(read by bothwebandsidekiq):Terminal window SMTP_SERVER=posthornSMTP_PORT=2525SMTP_LOGIN=SMTP_PASSWORD=SMTP_AUTH_METHOD=noneSMTP_OPENSSL_VERIFY_MODE=noneSMTP_ENABLE_STARTTLS=neverSMTP_FROM_ADDRESS=Mastodon <notifications@yourdomain.com>SMTP_ENABLE_STARTTLS=neverand the emptySMTP_LOGIN/SMTP_PASSWORDare the important ones — they keep Mastodon from trying to upgrade the connection or authenticate, which matches the plaintext-on-the-internal-network listener. -
Test the delivery path with Mastodon’s built-in mailer task:
Terminal window docker compose run --rm web bin/tootctl email test you@example.comPosthorn logs
smtp_submission_sentwith the provider’stransport_message_id.
Common gotchas
Section titled “Common gotchas”| Symptom | Cause | Fix |
|---|---|---|
| Sidekiq mailer job retries with a TLS error | SMTP_ENABLE_STARTTLS not never | Set SMTP_ENABLE_STARTTLS=never (older Mastodon: SMTP_TLS=false, SMTP_ENABLE_STARTTLS_AUTO=false) |
530 5.7.0 Must issue STARTTLS first from Posthorn | Listener has require_tls = true | Set require_tls = false for the internal-network deployment |
550 5.7.1 Sender not authorized | SMTP_FROM_ADDRESS domain not in allowed_senders | Add *@yourdomain.com |
Mail sends from sidekiq, so check its logs (not web) when debugging delivery. See Internal SMTP relay for the trust model behind auth_required = "none".