fix(邮件发送): 适配 MailerSend SMTP 并迁移基础设施至 1Panel

Motivation:
将通知服务部署到 1Panel 生产环境,集成 MailerSend 邮件服务,修复 SMTP 协议中 MAIL FROM 命令因携带显示名而导致的发送失败问题。

Changes:

* 修复 SMTP MAIL FROM 命令传入带显示名的完整地址导致发送失败的问题,新增 extractEmailAddr 提取裸地址
* 更新 SMTP 配置为 MailerSend 真实凭证,From 地址适配其域名验证要求
* 移除 docker-compose 中内置的 MySQL/Redis 服务,改为依赖外部 1Panel 基础设施
* 更新环境变量为生产环境实际凭据
This commit is contained in:
2026-07-31 17:28:35 +08:00
parent 6e69f6cdc3
commit cecf8e72f5
3 changed files with 20 additions and 48 deletions
+5 -4
View File
@@ -6,7 +6,7 @@ database:
host: "mysql" host: "mysql"
port: 3306 port: 3306
user: "root" user: "root"
password: "${DB_PASSWORD:-7Qay8mksnwrCffGi}" password: "${DB_PASSWORD:-notify}"
database: "notification" database: "notification"
redis: redis:
@@ -16,11 +16,12 @@ redis:
db: 0 db: 0
smtp: smtp:
host: "smtp.example.com" host: "smtp.mailersend.net"
port: 587 port: 587
user: "notify@example.com" user: "MS_hqXDZ6@516886.xyz"
password: "${SMTP_PASSWORD:-}" password: "${SMTP_PASSWORD:-}"
from: "Notification Service <notify@example.com>" # MailerSend 要求 from 域名已验证;未验证 516886.xyz 前先用试用域名
from: "Notification Service <MS_hqXDZ6@516886.xyz>"
rate_limit: rate_limit:
default: 100 default: 100
+2 -43
View File
@@ -1,58 +1,17 @@
version: '3.8' version: '3.8'
services: 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: api:
build: . build: .
ports: ports:
- "8080:8080" - "8080:8080"
environment: environment:
- DB_PASSWORD=notify - SMTP_PASSWORD=mssp.92QdSYS.3z0vklo982xl7qrx.tSlSpXs
- SMTP_PASSWORD= - DB_PASSWORD=7Qay8mksnwrCffGi
- NOTIFY_DATABASE_HOST=mysql
- NOTIFY_REDIS_HOST=redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped restart: unless-stopped
networks: networks:
- 1panel-network - 1panel-network
volumes:
mysql_data:
networks: networks:
1panel-network: 1panel-network:
external: true external: true
+13 -1
View File
@@ -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 <a@b.c>" are invalid here.
fromAddr := extractEmailAddr(s.cfg.From)
if err := client.Mail(fromAddr); err != nil {
return fmt.Errorf("mail: %w", err) return fmt.Errorf("mail: %w", err)
} }
for _, recipient := range to { for _, recipient := range to {
@@ -121,3 +123,13 @@ func buildEmail(from string, to []string, subject, body string) []byte {
sb.WriteString(body) sb.WriteString(body)
return []byte(sb.String()) return []byte(sb.String())
} }
// extractEmailAddr returns the bare address from "Name <addr@host>" 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)
}