feat(mail): 自 go-hua 迁入 SMTP 邮件发送封装

- SendToMail 支持收/抄/密送与回复地址、HTML 或文本正文
- 附测试、examples/mail 例程与包 README(注明不支持 465 加密端口)
This commit is contained in:
2026-09-20 12:29:38 +08:00
parent 9ff1a0e816
commit a6ba78c92c
4 changed files with 133 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# mail
SMTP 邮件发送封装:单函数 `SendToMail` 支持收件人/抄送/密送/回复地址,
`mailType` 选择 HTML 或纯文本正文。
> 迁移自 go-hua/mail,代码保持原样。
## 用法
```go
import "git.zeroonesoft.cn/golib/zogo/mail"
err := mail.SendToMail(
"user@example.com", // SMTP 用户名
"password-or-auth-code", // 密码/授权码
"smtp.example.com:25", // SMTP 服务器(host:port)
"user@example.com", // 发件人
"告警通知", // 主题
"<b>CPU 超限</b>", // 正文
"html", // "html" 或其他(纯文本)
"reply@example.com", // Reply-To
[]string{"a@example.com"}, // 收件人
[]string{"b@example.com"}, // 抄送
nil, // 密送
)
```
完整可运行例程:[examples/mail/main.go](../examples/mail/main.go)
## 注意
- 不支持 SSL/TLS 端口(465),走明文 SMTP 或 STARTTLS 由服务器协商;现在多数
公网邮箱要求授权码 + 加密端口,此包更适合内网 SMTP 中继。
- 邮件头未做 RFC 2047 编码,中文主题请确认服务器兼容(内网中继一般没问题)。
+40
View File
@@ -0,0 +1,40 @@
// Package mail 封装 SMTP 邮件发送。
package mail
import (
"net/smtp"
"strings"
)
// SendToMail 通过 SMTP 发送邮件
// user/password 为 SMTP 账号与密码(或授权码);host 如 "smtp.example.com:25"
// mailType 为 "html" 时正文按 HTML 发送,其他值按纯文本发送
// to/cc/bcc 分别为收件人/抄送/密送列表
func SendToMail(user, password, host, from, subject, body, mailType, replyToAddress string, to, cc, bcc []string) error {
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", user, password, hp[0])
var contentType string
if mailType == "html" {
contentType = "Content-Type: text/" + mailType + "; charset=UTF-8"
} else {
contentType = "Content-Type: text/plain" + "; charset=UTF-8"
}
ccAddress := strings.Join(cc, ";")
bccAddress := strings.Join(bcc, ";")
toAddress := strings.Join(to, ";")
msg := []byte("To: " + toAddress + "\r\nFrom: " + from + "\r\nSubject: " + subject + "\r\nReply-To: " + replyToAddress + "\r\nCc: " + ccAddress + "\r\nBcc: " + bccAddress + "\r\n" + contentType + "\r\n\r\n" + body)
sendTo := MergeSlice(to, cc)
sendTo = MergeSlice(sendTo, bcc)
err := smtp.SendMail(host, auth, user, sendTo, msg)
return err
}
// MergeSlice 合并两个字符串切片(用于合并收件人列表)
func MergeSlice(s1 []string, s2 []string) []string {
slice := make([]string, len(s1)+len(s2))
copy(slice, s1)
copy(slice[len(s1):], s2)
return slice
}
+22
View File
@@ -0,0 +1,22 @@
package mail
import "testing"
func TestMergeSlice(t *testing.T) {
got := MergeSlice([]string{"a@t.com", "b@t.com"}, []string{"c@t.com"})
if len(got) != 3 {
t.Fatalf("MergeSlice 长度 = %d, want 3", len(got))
}
want := map[string]bool{"a@t.com": true, "b@t.com": true, "c@t.com": true}
for _, s := range got {
if !want[s] {
t.Errorf("MergeSlice 结果包含意外元素 %q", s)
}
}
}
func TestMergeSliceEmpty(t *testing.T) {
if got := MergeSlice(nil, nil); len(got) != 0 {
t.Errorf("空输入 MergeSlice = %v, want 空", got)
}
}