diff --git a/examples/mail/main.go b/examples/mail/main.go
new file mode 100644
index 0000000..b58cf74
--- /dev/null
+++ b/examples/mail/main.go
@@ -0,0 +1,37 @@
+// mail 包示例:SMTP 发送邮件(SendToMail)与收件人列表合并(MergeSlice)
+package main
+
+import (
+ "fmt"
+
+ "git.zeroonesoft.cn/golib/zogo/mail"
+)
+
+func main() {
+ // MergeSlice:合并收件人列表(to + cc + bcc 一次性提交给 SMTP)
+ merged := mail.MergeSlice([]string{"a@test.com", "b@test.com"}, []string{"c@test.com"})
+ fmt.Println("合并收件人:", merged)
+
+ // SendToMail 参数说明:
+ // user SMTP 账号
+ // password SMTP 密码/授权码
+ // host SMTP 服务器,如 "smtp.example.com:25"
+ // from 发件人地址
+ // subject 邮件主题
+ // body 邮件正文
+ // mailType "html" 为 HTML 邮件,其他值为纯文本
+ // replyToAddress 回复地址
+ // to / cc / bcc 收件人 / 抄送 / 密送列表
+ //
+ // 填入真实 SMTP 账号后调用即可发送:
+ //
+ // err := mail.SendToMail(
+ // "user@example.com", "password", "smtp.example.com:25",
+ // "user@example.com", "测试邮件", "
hello
", "html",
+ // "reply@example.com",
+ // []string{"to@example.com"}, []string{"cc@example.com"}, []string{},
+ // )
+ // if err != nil {
+ // fmt.Println("发送失败:", err)
+ // }
+}
diff --git a/mail/README.md b/mail/README.md
new file mode 100644
index 0000000..d1c7736
--- /dev/null
+++ b/mail/README.md
@@ -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", // 发件人
+ "告警通知", // 主题
+ "CPU 超限", // 正文
+ "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 编码,中文主题请确认服务器兼容(内网中继一般没问题)。
diff --git a/mail/mail.go b/mail/mail.go
new file mode 100644
index 0000000..a1023ad
--- /dev/null
+++ b/mail/mail.go
@@ -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
+}
diff --git a/mail/mail_test.go b/mail/mail_test.go
new file mode 100644
index 0000000..110eb2c
--- /dev/null
+++ b/mail/mail_test.go
@@ -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)
+ }
+}