chore(mail): 优化邮件投递超时与服务配置

- 为 SMTP 连接与前端发信请求增加更明确的超时控制。
- 调整 Postfix 与 Rspamd 代理配置,缩短相关处理超时并统一运行参数。
This commit is contained in:
LanQin
2026-06-16 22:28:04 +08:00
parent 4882a65e43
commit b0cc425f61
4 changed files with 35 additions and 27 deletions
+9 -3
View File
@@ -16,6 +16,8 @@ import (
"time"
)
const smtpSessionTimeout = 45 * time.Second
type MIMEMessage struct {
From string
To []string
@@ -142,10 +144,11 @@ func sendSMTPWithConfig(cfg Config, from string, recipients []string, mimeBytes
}
func sendSMTPPlain(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error {
conn, err := net.Dial("tcp", addr)
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
if err != nil {
return err
}
_ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout))
client, err := smtp.NewClient(conn, host)
if err != nil {
_ = conn.Close()
@@ -156,10 +159,12 @@ func sendSMTPPlain(addr, host string, auth smtp.Auth, from string, recipients []
}
func sendSMTPImplicitTLS(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error {
conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12})
dialer := &net.Dialer{Timeout: 10 * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12})
if err != nil {
return err
}
_ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout))
client, err := smtp.NewClient(conn, host)
if err != nil {
_ = conn.Close()
@@ -170,10 +175,11 @@ func sendSMTPImplicitTLS(addr, host string, auth smtp.Auth, from string, recipie
}
func sendSMTPStartTLS(addr, host string, auth smtp.Auth, from string, recipients []string, mimeBytes []byte) error {
conn, err := net.Dial("tcp", addr)
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
if err != nil {
return err
}
_ = conn.SetDeadline(time.Now().Add(smtpSessionTimeout))
client, err := smtp.NewClient(conn, host)
if err != nil {
_ = conn.Close()
+8 -6
View File
@@ -2,17 +2,19 @@ import type { User, AdminUser, AdminOverview, Domain, Mailbox, Alias, MailFolder
export * from "./api-types"
const REQUEST_TIMEOUT_MS = 15_000
const MAIL_DELIVERY_TIMEOUT_MS = 60_000
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
async function request<T>(path: string, init: RequestInit & { timeoutMs?: number } = {}): Promise<T> {
const { timeoutMs, ...requestInit } = init
const controller = new AbortController()
const timeout = window.setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
const externalSignal = init.signal
const timeout = window.setTimeout(() => controller.abort(), timeoutMs || REQUEST_TIMEOUT_MS)
const externalSignal = requestInit.signal
if (externalSignal) {
if (externalSignal.aborted) controller.abort()
else externalSignal.addEventListener("abort", () => controller.abort(), { once: true })
}
try {
const res = await fetch(path, { credentials: "include", headers: { "Content-Type": "application/json", ...(init.headers || {}) }, ...init, signal: controller.signal })
const res = await fetch(path, { credentials: "include", headers: { "Content-Type": "application/json", ...(requestInit.headers || {}) }, ...requestInit, signal: controller.signal })
if (!res.ok) {
let message = `${res.status} ${res.statusText}`
try { const body = await res.json(); message = body.error || message } catch {}
@@ -83,7 +85,7 @@ export const api = {
adminMessage: (id: string) => request<MailMessage>(`/api/admin/messages/${id}`),
systemSettings: () => request<SystemSettings>("/api/admin/settings"),
updateSystemSettings: (payload: SystemSettingsPayload) => request<SystemSettings>("/api/admin/settings", { method: "POST", body: JSON.stringify(payload) }),
testSmtp: (to: string) => request<{ ok: boolean }>("/api/admin/settings/test-smtp", { method: "POST", body: JSON.stringify({ to }) }),
testSmtp: (to: string) => request<{ ok: boolean }>("/api/admin/settings/test-smtp", { method: "POST", body: JSON.stringify({ to }), timeoutMs: MAIL_DELIVERY_TIMEOUT_MS }),
mailTemplates: () => request<ListResponse<MailTemplate>>("/api/admin/mail-templates"),
updateMailTemplate: (key: string, payload: { subject: string; bodyText: string; bodyHtml: string }) => request<MailTemplate>(`/api/admin/mail-templates/${encodeURIComponent(key)}`, { method: "POST", body: JSON.stringify(payload) }),
resetMailTemplate: (key: string) => request<MailTemplate>(`/api/admin/mail-templates/${encodeURIComponent(key)}/reset`, { method: "POST" }),
@@ -112,7 +114,7 @@ export const api = {
return request<ListResponse<MailMessage>>(`/api/mail/starred?${params.toString()}`)
},
message: (id: string, options: { markRead?: boolean } = {}) => request<MailMessage>(`/api/mail/messages/${id}${options.markRead === false ? "?markRead=0" : ""}`),
send: (payload: SendPayload) => request<MailMessage>("/api/mail/send", { method: "POST", body: JSON.stringify(payload) }),
send: (payload: SendPayload) => request<MailMessage>("/api/mail/send", { method: "POST", body: JSON.stringify(payload), timeoutMs: MAIL_DELIVERY_TIMEOUT_MS }),
markRead: (id: string, read: boolean) => request<{ ok: boolean }>(`/api/mail/messages/${id}/mark-read`, { method: "POST", body: JSON.stringify({ read }) }),
star: (id: string, starred: boolean) => request<{ ok: boolean }>(`/api/mail/messages/${id}/star`, { method: "POST", body: JSON.stringify({ starred }) }),
addLabel: (id: string, payload: { name: string; color?: string }) => request<{ labels: MailLabel[] }>(`/api/mail/messages/${id}/labels`, { method: "POST", body: JSON.stringify(payload) }),
+17 -17
View File
@@ -4,26 +4,26 @@ submission inet n - n - - smtpd
-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
pickup unix n - n 60 1 pickup
cleanup unix n - n - 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
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 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
smtp unix - - n - - smtp
relay unix - - n - - smtp
showq unix n - n - - showq
error unix - - n - - error
retry unix - - n - - error
discard unix - - n - - 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
lmtp unix - - n - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
+1 -1
View File
@@ -1,6 +1,6 @@
bind_socket = "0.0.0.0:11332";
milter = yes;
timeout = 120s;
timeout = 10s;
upstream "local" {
default = yes;