Skip to content

Self-hosted Vaultwarden via Posthorn

You’re running Vaultwarden, the lightweight Bitwarden-compatible server. It sends email for new-device login notifications, email verification, organization invitations, emergency access, and email 2FA. On a cloud host that blocks outbound SMTP — or when you’d rather not hand your password manager the provider credentials — Posthorn’s SMTP listener accepts Vaultwarden’s connection on a private Docker network and forwards over HTTPS to whichever transactional provider you’ve chosen.

At the end: Vaultwarden talks to posthorn:2525 over a private Docker network with no TLS and no AUTH, and Posthorn forwards via the configured HTTP transport to Postmark, Resend, Mailgun, or SES.

Docker hostPostmark · ResendMailgun · SESVaultwardenPosthornSMTP listener (auth=none) SMTP on posthorn:2525(internal network)HTTPS
  1. Put Posthorn and Vaultwarden on the same private Docker network (no ports: exposure for Posthorn — see the Authentik recipe for the identical Posthorn service block):

    # docker-compose.yml (excerpt)
    networks:
    internal:
    internal: true
    services:
    vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    networks: [internal]
    # ... your volumes, DOMAIN, ADMIN_TOKEN, etc. ...
  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 = "postmark"
    [smtp_listener.transport.settings]
    api_key = "${env.POSTMARK_API_KEY}"
  3. Point Vaultwarden at Posthorn via environment. Vaultwarden enables SMTP as soon as SMTP_HOST and SMTP_FROM are set:

    environment:
    SMTP_HOST: posthorn
    SMTP_PORT: "2525"
    SMTP_SECURITY: "off" # plaintext on the internal network — no STARTTLS
    SMTP_FROM: "vaultwarden@yourdomain.com"
    SMTP_FROM_NAME: "Vaultwarden"
    # SMTP_USERNAME / SMTP_PASSWORD intentionally unset — the listener performs no AUTH
  4. Test from the admin panel. With ADMIN_TOKEN set, open /adminSMTP Email SettingsSend test email. Posthorn logs the session:

    {"msg":"smtp_session_open","ingress":"smtp","tls":"no"}
    {"msg":"smtp_submission_sent","transport_message_id":"..."}
SymptomCauseFix
Vaultwarden log: Connection refused connecting to SMTPMail dialed port 25, not 2525Set SMTP_PORT="2525" explicitly — with SMTP_SECURITY=off the default is 25
TLS / STARTTLS handshake error in the Vaultwarden logSMTP_SECURITY still starttls or force_tlsSet SMTP_SECURITY="off"; the listener runs plaintext on the internal network
550 5.7.1 Sender not authorized from PosthornSMTP_FROM domain isn’t in allowed_sendersAdd *@yourdomain.com (or the exact address)
No Send test email buttonAdmin panel disabledSet ADMIN_TOKEN (argon2 hash recommended) and reload /admin
Test email works but verification links are wrongDOMAIN unset or wrongSet DOMAIN=https://vault.yourdomain.com — it drives the links inside the mail, not the SMTP path

Only the mail path runs through Posthorn; DOMAIN, ADMIN_TOKEN, and the rest of Vaultwarden’s config are unchanged. See Internal SMTP relay for the trust model behind auth_required = "none".