feat(email): 初始化自建邮箱 MVP

- 新增 Go + SQLite 后端,支持登录、域名、邮箱、别名、联系人、规则、统计与邮件收发。
- 新增 Webmail 前端,支持多邮箱切换、邮件列表/阅读/写信、附件、搜索、主题切换与个人中心。
- 新增 Docker Compose 部署骨架,补充 Postfix、Dovecot、OpenDKIM、Nginx 配置与部署说明。
This commit is contained in:
LanQin
2026-06-14 01:07:48 +08:00
commit 3ef8caa319
85 changed files with 13649 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends postfix postfix-sqlite ca-certificates rsyslog && rm -rf /var/lib/apt/lists/*
COPY main.cf /etc/postfix/main.cf
COPY master.cf /etc/postfix/master.cf
COPY sqlite-*.cf /etc/postfix/
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 25 587
CMD ["/entrypoint.sh"]
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
set -eu
: "${LANQIN_PUBLIC_HOSTNAME:=mail.example.com}"
postconf -e "myhostname = ${LANQIN_PUBLIC_HOSTNAME}"
postconf -e "myorigin = ${LANQIN_PUBLIC_HOSTNAME}"
postconf -e "smtpd_milters = inet:opendkim:8891"
postconf -e "non_smtpd_milters = inet:opendkim:8891"
postfix check
exec postfix start-fg
+30
View File
@@ -0,0 +1,30 @@
compatibility_level = 3.6
myhostname = mail.example.com
myorigin = $myhostname
mydestination = localhost
inet_interfaces = all
inet_protocols = all
# SQLite maps read the same LanQin DB created by the Go API.
virtual_mailbox_domains = sqlite:/etc/postfix/sqlite-domains.cf
virtual_mailbox_maps = sqlite:/etc/postfix/sqlite-mailboxes.cf
virtual_alias_maps = sqlite:/etc/postfix/sqlite-aliases.cf
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
# Submission auth via Dovecot.
smtpd_sasl_type = dovecot
smtpd_sasl_path = inet:dovecot:12345
smtpd_sasl_auth_enable = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
# DKIM milter.
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:opendkim:8891
non_smtpd_milters = inet:opendkim:8891
+29
View File
@@ -0,0 +1,29 @@
smtp inet n - y - - smtpd
submission inet n - y - - 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
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - y 1000? 1 tlsmgr
rewrite unix - - y - - trivial-rewrite
bounce unix - - y - 0 bounce
defer unix - - y - 0 bounce
trace unix - - y - 0 bounce
verify unix - - y - 1 verify
flush unix n - y 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - y - - smtp
relay unix - - y - - smtp
showq unix n - y - - showq
error unix - - y - - error
retry unix - - y - - error
discard unix - - y - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - y - - lmtp
anvil unix - - y - 1 anvil
scache unix - - y - 1 scache
+2
View File
@@ -0,0 +1,2 @@
dbpath = /data/lanqin.db
query = SELECT destination FROM aliases WHERE source='%s' AND enabled=1 UNION SELECT address FROM mailboxes WHERE address='%s' AND status='active'
+2
View File
@@ -0,0 +1,2 @@
dbpath = /data/lanqin.db
query = SELECT 1 FROM domains WHERE name='%s' AND status='active'
+2
View File
@@ -0,0 +1,2 @@
dbpath = /data/lanqin.db
query = SELECT 'vhosts/' || substr(address, instr(address, '@') + 1) || '/' || local_part || '/Maildir/' FROM mailboxes WHERE address='%s' AND status='active'