feat(mail): 支持 SMTP Submission 由 API 接管
- 新增 `465/587` 提交服务与 TLS 配置,支持 StartTLS 和隐式 TLS。 - 提交时校验认证邮箱、权限和 `From` 一致性,先写入 `Sent` 再转发到后端 SMTP。 - 增加 `Message-ID` 去重与失败回滚,避免重复保存发送副本。 - 调整部署与文档,移除 Postfix 的 submission 监听,改由 LanQin API 对外提供提交端口。
This commit is contained in:
@@ -93,6 +93,11 @@ LANQIN_SMTP_PASSWORD=
|
||||
# 外部 SMTP 要求 STARTTLS / TLS 时改 true;本机 Postfix 默认 false。
|
||||
LANQIN_SMTP_REQUIRE_TLS=false
|
||||
|
||||
# 第三方客户端 SMTP 提交,由 LanQin API 监听 587/465。
|
||||
LANQIN_SUBMISSION_ADDR=:587
|
||||
LANQIN_SUBMISSION_TLS_ADDR=:465
|
||||
LANQIN_SUBMISSION_MAX_MESSAGE_MB=35
|
||||
|
||||
# =========================
|
||||
# 收件 / Maildir 同步
|
||||
# =========================
|
||||
|
||||
+4
-3
@@ -128,12 +128,12 @@ docker compose -f docker-compose.stack.yml -f docker-compose.stack.build.yml up
|
||||
- Rspamd 会周期性从 SQLite 导出域名 DKIM 私钥到容器内 `/var/lib/rspamd/dkim`。
|
||||
- Go API 是 Webmail 和管理后台入口;浏览器不直接连接 SMTP/IMAP/POP3。
|
||||
- Go API 会读取 `LANQIN_MAILDIR_ROOT=/var/mail/vhosts`,周期扫描 Maildir,把 Postfix/Dovecot 入站邮件同步成 Webmail 索引。
|
||||
- 第三方客户端可通过 SMTP `465/587` 发信;Webmail 内的“已发送”由 Webmail API 发信流程写入。
|
||||
- 第三方客户端可通过 LanQin API 提供的 SMTP `465/587` 发信;Webmail/API 和第三方客户端的“已发送”都由 API 写入,客户端后续 IMAP APPEND 到 Sent 会按 `Message-ID` 去重。
|
||||
|
||||
## 邮件客户端 TLS 证书
|
||||
|
||||
Web 站点可以由宿主机 Nginx / 宝塔反代到容器 `80`,但 SMTP/IMAP/POP3 端口不会使用 Web 反代的证书。
|
||||
如果第三方客户端连接 `465/587/993/995` 时提示证书是 `localhost`,说明 Postfix/Dovecot 仍在使用容器自带的测试证书。
|
||||
如果第三方客户端连接 `465/587/993/995` 时提示证书是 `localhost`,说明 LanQin API 或 Dovecot 仍在使用容器自带的测试证书。
|
||||
|
||||
生产环境请把域名证书挂载进容器,并在 `.env` 指向证书文件:
|
||||
|
||||
@@ -174,7 +174,8 @@ LANQIN_SMTP_REQUIRE_TLS=false
|
||||
|
||||
```bash
|
||||
docker compose exec lanqin-email supervisorctl status
|
||||
docker compose exec lanqin-email postconf -M smtp/inet submission/inet
|
||||
docker compose exec lanqin-email postconf -M smtp/inet
|
||||
# SMTP 提交 465/587 由 LanQin API 提供,不再由 Postfix 监听。
|
||||
docker compose exec lanqin-email sqlite3 /data/lanqin.db "select key,value from system_settings where key like 'smtp%' order by key;"
|
||||
docker compose logs --tail=200 lanqin-email
|
||||
```
|
||||
|
||||
@@ -7,11 +7,14 @@ set -eu
|
||||
: "${LANQIN_ADDR:=127.0.0.1:8080}"
|
||||
: "${LANQIN_SMTP_HOST:=127.0.0.1}"
|
||||
: "${LANQIN_SMTP_PORT:=25}"
|
||||
: "${LANQIN_SUBMISSION_ADDR:=:587}"
|
||||
: "${LANQIN_SUBMISSION_TLS_ADDR:=:465}"
|
||||
: "${LANQIN_SUBMISSION_MAX_MESSAGE_MB:=35}"
|
||||
: "${LANQIN_MAILDIR_ROOT:=/var/mail/vhosts}"
|
||||
: "${LANQIN_TLS_CERT_FILE:=}"
|
||||
: "${LANQIN_TLS_KEY_FILE:=}"
|
||||
|
||||
export LANQIN_DATA_DIR LANQIN_DB_PATH LANQIN_ADDR LANQIN_SMTP_HOST LANQIN_SMTP_PORT LANQIN_MAILDIR_ROOT
|
||||
export LANQIN_DATA_DIR LANQIN_DB_PATH LANQIN_ADDR LANQIN_SMTP_HOST LANQIN_SMTP_PORT LANQIN_SUBMISSION_ADDR LANQIN_SUBMISSION_TLS_ADDR LANQIN_SUBMISSION_MAX_MESSAGE_MB LANQIN_MAILDIR_ROOT LANQIN_TLS_CERT_FILE LANQIN_TLS_KEY_FILE
|
||||
|
||||
addgroup --system --gid 5000 vmail 2>/dev/null || true
|
||||
adduser --system --uid 5000 --gid 5000 --home /var/mail/vhosts --no-create-home vmail 2>/dev/null || true
|
||||
@@ -47,7 +50,6 @@ postconf -e "myorigin = ${LANQIN_PUBLIC_HOSTNAME}"
|
||||
postconf -e "smtpd_tls_cert_file = ${TLS_CERT}"
|
||||
postconf -e "smtpd_tls_key_file = ${TLS_KEY}"
|
||||
postconf -e "virtual_transport = lmtp:inet:127.0.0.1:24"
|
||||
postconf -e "smtpd_sasl_path = inet:127.0.0.1:12345"
|
||||
postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"
|
||||
postconf -e "smtpd_milters = inet:127.0.0.1:11332"
|
||||
postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
|
||||
|
||||
@@ -17,5 +17,5 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y --no-install-recommends ca-certificates tzdata
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/lanqin-api /usr/local/bin/lanqin-api
|
||||
EXPOSE 8080
|
||||
EXPOSE 8080 465 587
|
||||
CMD ["lanqin-api"]
|
||||
|
||||
@@ -3,8 +3,15 @@ services:
|
||||
image: ${LANQIN_API_IMAGE:-ghcr.io/lanqin996/lanqin-email-api:latest}
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./data:/data:ro
|
||||
- ./data:/data
|
||||
- ./mail:/var/mail/vhosts:ro
|
||||
# 生产环境如需第三方客户端校验证书,请取消下面挂载的注释,并在 .env 配置:
|
||||
# LANQIN_TLS_CERT_FILE=/certs/fullchain.pem
|
||||
# LANQIN_TLS_KEY_FILE=/certs/privkey.pem
|
||||
# - /etc/letsencrypt/live/${LANQIN_PUBLIC_HOSTNAME}:/certs:ro
|
||||
ports:
|
||||
- "465:465"
|
||||
- "587:587"
|
||||
depends_on:
|
||||
- dovecot
|
||||
- postfix
|
||||
@@ -36,8 +43,6 @@ services:
|
||||
# - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
ports:
|
||||
- "25:25"
|
||||
- "465:465"
|
||||
- "587:587"
|
||||
depends_on:
|
||||
- dovecot
|
||||
- rspamd
|
||||
|
||||
@@ -11,5 +11,5 @@ COPY master.cf /etc/postfix/master.cf
|
||||
COPY sqlite-*.cf /etc/postfix/
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
EXPOSE 25 465 587
|
||||
EXPOSE 25
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
@@ -14,13 +14,11 @@ virtual_transport = lmtp:inet:dovecot:24
|
||||
virtual_mailbox_base = /var/mail/vhosts
|
||||
|
||||
smtpd_banner = $myhostname ESMTP LanQin Email
|
||||
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
|
||||
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
|
||||
smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination
|
||||
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
|
||||
|
||||
# Submission auth via Dovecot.
|
||||
smtpd_sasl_type = dovecot
|
||||
smtpd_sasl_path = inet:dovecot:12345
|
||||
smtpd_sasl_auth_enable = yes
|
||||
# 465/587 提交由 LanQin API 处理;Postfix 25 只负责入站和内部 relay。
|
||||
smtpd_sasl_auth_enable = no
|
||||
smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
smtpd_tls_security_level = may
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
smtp inet n - n - - smtpd
|
||||
submission inet n - n - - smtpd
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_tls_security_level=may
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
smtps inet n - n - - smtpd
|
||||
-o syslog_name=postfix/smtps
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
pickup unix n - n 60 1 pickup
|
||||
cleanup unix n - n - 0 cleanup
|
||||
qmgr unix n - n 300 1 qmgr
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
dbpath = /data/lanqin.db
|
||||
query = SELECT local_part || '+Sent@' || substr(address, instr(address, '@') + 1) FROM mailboxes WHERE lower(address)=lower('%s') AND status='active'
|
||||
Reference in New Issue
Block a user