Dovecot SMTP Submission Proxy
Dovecot deployment acting as a pure SMTP submission proxy, companion to imap-proxy: it listens on your hostname (e.g. smtp.example.com) with its own SSL certificate, accepts and terminates the client connections (implicit TLS on 465, STARTTLS on 587), then connects to the destination server (e.g. smtp.example.net) as if it were the client, over the destination's own SSL, and forwards the traffic bit-by-bit in both directions.
Your mail clients (Thunderbird, Apple Mail, the Gmail app, ...) are configured once against your hostname and certificate; which provider actually serves the mail is decided in one config line and can be changed at will — no need to buy the provider a certificate for your domain or pay for custom-hostname setups.
How it works
Dovecot's login proxy is used with a static passdb: any username is accepted, no password is verified locally (nopassword), and the login is forwarded (credentials included) to the destination submission server, which performs the real authentication. Once the remote login succeeds, dovecot becomes a dumb pipe between client and destination. No mail is ever stored or queued locally: if the destination rejects a message, the client sees the rejection directly.
Because the password must be forwarded, clients have to use cleartext mechanisms (PLAIN / LOGIN); dovecot only allows them after TLS is negotiated, so nothing travels unencrypted.
Port 25 is intentionally not handled: it is server-to-server traffic driven by your MX records, which can point straight at the provider — no certificate for your client-facing hostname is involved there.
Configuration
Copy example.env to .env and adjust:
SSL_CERT/SSL_KEY: certificate and key for the hostname the proxy serves (e.g. Let's Encrypt live paths).SUBMISSION_EXPOSE/SMTPS_EXPOSE: host bindings for ports 587/465.
Copy example.dovecot.conf to ${DOVECOT_CONF} (default ./data/dovecot.conf) and set host in the passdb static block to your destination submission server (port 465 with ssl = yes, or port 587 with starttls = yes). The destination certificate is verified against the system CA bundle (ssl_client_ca_file); use ssl = any-cert instead of ssl = yes if the destination has a self-signed certificate.
Usage
cp example.env .env
mkdir -p data && cp example.dovecot.conf data/dovecot.conf
$EDITOR .env data/dovecot.conf
docker compose up -d
Testing
openssl s_client -connect smtp.example.com:465 -quiet
# then authenticate as an existing user on the destination server:
EHLO test
AUTH PLAIN <base64 of "\0user@example.com\0password">
(generate the AUTH string with printf '\0user@example.com\0password' | base64)
Notes
The -root image flavor is used so dovecot can read certificates that are only readable by root (as the Let's Encrypt live folder is); dovecot itself still drops privileges per-service. If your certificates are world-readable you can drop the -root suffix from DOVECOT_VERSION.