From cecf8e72f5a83a1872aa4e4c28887a68515bddff Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 31 Jul 2026 17:28:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=82=AE=E4=BB=B6=E5=8F=91=E9=80=81):=20?= =?UTF-8?q?=E9=80=82=E9=85=8D=20MailerSend=20SMTP=20=E5=B9=B6=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD=E8=87=B3=201Panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: 将通知服务部署到 1Panel 生产环境,集成 MailerSend 邮件服务,修复 SMTP 协议中 MAIL FROM 命令因携带显示名而导致的发送失败问题。 Changes: * 修复 SMTP MAIL FROM 命令传入带显示名的完整地址导致发送失败的问题,新增 extractEmailAddr 提取裸地址 * 更新 SMTP 配置为 MailerSend 真实凭证,From 地址适配其域名验证要求 * 移除 docker-compose 中内置的 MySQL/Redis 服务,改为依赖外部 1Panel 基础设施 * 更新环境变量为生产环境实际凭据 --- config/config.yaml | 9 ++++---- docker-compose.yml | 45 ++------------------------------------- internal/adapter/email.go | 14 +++++++++++- 3 files changed, 20 insertions(+), 48 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index af58ddd..1bcc820 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -6,7 +6,7 @@ database: host: "mysql" port: 3306 user: "root" - password: "${DB_PASSWORD:-7Qay8mksnwrCffGi}" + password: "${DB_PASSWORD:-notify}" database: "notification" redis: @@ -16,11 +16,12 @@ redis: db: 0 smtp: - host: "smtp.example.com" + host: "smtp.mailersend.net" port: 587 - user: "notify@example.com" + user: "MS_hqXDZ6@516886.xyz" password: "${SMTP_PASSWORD:-}" - from: "Notification Service " + # MailerSend 要求 from 域名已验证;未验证 516886.xyz 前先用试用域名 + from: "Notification Service " rate_limit: default: 100 diff --git a/docker-compose.yml b/docker-compose.yml index 9800cf0..b7becf5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,58 +1,17 @@ version: '3.8' services: - mysql: - image: mysql:8.0 - environment: - MYSQL_ROOT_PASSWORD: rootpass - MYSQL_DATABASE: notification - MYSQL_USER: notify - MYSQL_PASSWORD: notify - ports: - - "3306:3306" - volumes: - - mysql_data:/var/lib/mysql - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 5s - timeout: 3s - retries: 10 - networks: - - 1panel-network - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 5 - networks: - - 1panel-network - api: build: . ports: - "8080:8080" environment: - - DB_PASSWORD=notify - - SMTP_PASSWORD= - - NOTIFY_DATABASE_HOST=mysql - - NOTIFY_REDIS_HOST=redis - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy + - SMTP_PASSWORD=mssp.92QdSYS.3z0vklo982xl7qrx.tSlSpXs + - DB_PASSWORD=7Qay8mksnwrCffGi restart: unless-stopped networks: - 1panel-network -volumes: - mysql_data: - networks: 1panel-network: external: true diff --git a/internal/adapter/email.go b/internal/adapter/email.go index 17e21da..064ef0e 100644 --- a/internal/adapter/email.go +++ b/internal/adapter/email.go @@ -91,7 +91,9 @@ func (s *EmailSender) authAndSend(client *smtp.Client, to []string, msg []byte) } } - if err := client.Mail(s.cfg.From); err != nil { + // MAIL FROM must be a bare address; display names like "Name " are invalid here. + fromAddr := extractEmailAddr(s.cfg.From) + if err := client.Mail(fromAddr); err != nil { return fmt.Errorf("mail: %w", err) } for _, recipient := range to { @@ -121,3 +123,13 @@ func buildEmail(from string, to []string, subject, body string) []byte { sb.WriteString(body) return []byte(sb.String()) } + +// extractEmailAddr returns the bare address from "Name " or the original string. +func extractEmailAddr(from string) string { + start := strings.LastIndex(from, "<") + end := strings.LastIndex(from, ">") + if start >= 0 && end > start { + return strings.TrimSpace(from[start+1 : end]) + } + return strings.TrimSpace(from) +}