Skip to content

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.

Docker hostPostmark · ResendMailgun · SESMastodonweb + sidekiqPosthornSMTP listener (auth=none) SMTP on posthorn:2525(internal network)HTTPS
  1. Same private Docker network, no host-port exposure for Posthorn (see the Authentik recipe for the compose skeleton — it’s identical).

  2. Write posthorn.toml.

    [smtp_listener]
    listen = ":2525"
    auth_required = "none"
    require_tls = false
    trusted_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}"
  3. Point Mastodon at Posthorn in its .env.production (read by both web and sidekiq):

    Terminal window
    SMTP_SERVER=posthorn
    SMTP_PORT=2525
    SMTP_LOGIN=
    SMTP_PASSWORD=
    SMTP_AUTH_METHOD=none
    SMTP_OPENSSL_VERIFY_MODE=none
    SMTP_ENABLE_STARTTLS=never
    SMTP_FROM_ADDRESS=Mastodon <notifications@yourdomain.com>

    SMTP_ENABLE_STARTTLS=never and the empty SMTP_LOGIN/SMTP_PASSWORD are the important ones — they keep Mastodon from trying to upgrade the connection or authenticate, which matches the plaintext-on-the-internal-network listener.

  4. Test the delivery path with Mastodon’s built-in mailer task:

    Terminal window
    docker compose run --rm web bin/tootctl email test you@example.com

    Posthorn logs smtp_submission_sent with the provider’s transport_message_id.

SymptomCauseFix
Sidekiq mailer job retries with a TLS errorSMTP_ENABLE_STARTTLS not neverSet SMTP_ENABLE_STARTTLS=never (older Mastodon: SMTP_TLS=false, SMTP_ENABLE_STARTTLS_AUTO=false)
530 5.7.0 Must issue STARTTLS first from PosthornListener has require_tls = trueSet require_tls = false for the internal-network deployment
550 5.7.1 Sender not authorizedSMTP_FROM_ADDRESS domain not in allowed_sendersAdd *@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".