added dovecot smtp proxy

This commit is contained in:
2026-08-26 21:31:41 +02:00
parent 83fe880faf
commit 11f5f48f0d
5 changed files with 106 additions and 1 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.env
data
docker-compose.override.yml

View File

@@ -1 +1,39 @@
# smtp-proxy
# Dovecot SMTP Submission Proxy
Dovecot deployment acting as a pure SMTP submission proxy, companion to [imap-proxy](../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
```sh
cp example.env .env
mkdir -p data && cp example.dovecot.conf data/dovecot.conf
$EDITOR .env data/dovecot.conf
docker compose up -d
```
## Testing
```sh
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`.

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
---
services:
dovecot:
image: dovecot/dovecot:${DOVECOT_VERSION}
restart: unless-stopped
ports:
- ${SUBMISSION_EXPOSE}:587
- ${SMTPS_EXPOSE}:465
volumes:
- ${DOVECOT_CONF}:/etc/dovecot/dovecot.conf:ro
- ${SSL_CERT}:/etc/dovecot/ssl/cert.pem:ro
- ${SSL_KEY}:/etc/dovecot/ssl/key.pem:ro

43
example.dovecot.conf Normal file
View File

@@ -0,0 +1,43 @@
# Dovecot SMTP submission proxy configuration (dovecot 2.4 syntax)
#
# Terminates SSL/TLS for the local hostname (e.g. smtp.example.com) and
# proxies every login, credentials included, to the destination server
# (e.g. smtp.example.net) over its own SSL/TLS. After the login succeeds,
# dovecot forwards the connection bit-by-bit in both directions.
dovecot_config_version = 2.4.0
dovecot_storage_version = 2.4.0
protocols = submission
log_path = /dev/stdout
ssl = yes
ssl_server_cert_file = /etc/dovecot/ssl/cert.pem
ssl_server_key_file = /etc/dovecot/ssl/key.pem
ssl_min_protocol = TLSv1.2
ssl_client_ca_file = /etc/ssl/certs/ca-certificates.crt
auth_mechanisms = plain login
service submission-login {
inet_listener submission {
port = 587
}
inet_listener submissions {
port = 465
ssl = yes
}
}
# Destination submission server. "ssl = yes" + port 465 = implicit TLS;
# for a STARTTLS destination use port 587 with "starttls = yes" instead
# of the ssl field.
passdb static {
fields {
nopassword = yes
proxy = yes
host = smtp.example.net
port = 465
ssl = yes
}
}

9
example.env Normal file
View File

@@ -0,0 +1,9 @@
# Dovecot
DOVECOT_VERSION=2.4.4-root
DOVECOT_CONF=./data/dovecot.conf
SUBMISSION_EXPOSE=587
SMTPS_EXPOSE=465
# SSL certificates
SSL_CERT=/etc/letsencrypt/live/smtp.example.com/fullchain.pem
SSL_KEY=/etc/letsencrypt/live/smtp.example.com/privkey.pem