更新
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# mailx 使用示例
|
||||
|
||||
本目录包含多个由浅入深的示例,方便快速接入。
|
||||
|
||||
## 示例总览
|
||||
|
||||
| 示例 | 难度 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| [`quickstart`](./quickstart) | 入门 | 最简 SMTP 发送,逐行注释,适合第一次接触 |
|
||||
| [`basic`](./basic) | 入门 | 单通道直接发送,含附件/抄送/回复地址 |
|
||||
| [`manager`](./manager) | 入门 | 多通道管理器:注册、路由、默认通道、临时指定配置 |
|
||||
| [`with_env`](./with_env) | 进阶 | 用环境变量管理多通道凭据,避免密钥写死在代码里 |
|
||||
| [`advanced`](./advanced) | 进阶 | 主备切换、实例遍历、日志注入、超时、内嵌图片、错误分类 |
|
||||
| [`custom_sender`](./custom_sender) | 进阶 | 实现自定义通道 + 自定义 Logger |
|
||||
|
||||
## 快速开始(5 分钟)
|
||||
|
||||
```bash
|
||||
# 1. 进入最简示例
|
||||
cd examples/quickstart
|
||||
|
||||
# 2. 打开 main.go,把里面的 4 个 SMTP 配置改成你自己的
|
||||
# (QQ 邮箱需先开启 SMTP 服务并获取授权码)
|
||||
|
||||
# 3. 运行
|
||||
go run main.go
|
||||
```
|
||||
|
||||
看到 `send success` 即表示发送成功。
|
||||
|
||||
## 运行方式
|
||||
|
||||
所有示例都在 mailx 主模块内,直接在示例目录执行 `go run .` 即可:
|
||||
|
||||
```bash
|
||||
# 示例:运行多通道管理器示例
|
||||
cd examples/manager
|
||||
go run .
|
||||
|
||||
# 示例:运行环境变量示例(先配置好 .env)
|
||||
cd examples/with_env
|
||||
go run .
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **凭据**:所有示例中的密钥都是占位符,运行前必须替换为真实值
|
||||
2. **SMTP 授权码**:QQ/163 等邮箱不是用邮箱登录密码,而是需要在邮箱后台开启 SMTP 服务后生成的授权码
|
||||
3. **AWS/阿里云**:需要先完成服务开通、域名验证(发件地址需为已验证身份)
|
||||
4. **`.env`**:`with_env` 示例依赖 `github.com/joho/godotenv`,首次运行前执行 `go get github.com/joho/godotenv`;`with_env/.env` 含敏感信息,勿提交 git
|
||||
@@ -0,0 +1,111 @@
|
||||
// advanced 展示生产环境常用的一些进阶能力。
|
||||
//
|
||||
// 覆盖以下内容:
|
||||
// 1. 同一通道类型(smtp)注册多份不同配置(主备切换)
|
||||
// 2. 遍历当前注册的通道实例信息
|
||||
// 3. 注入自定义 Logger(日志可插拔)
|
||||
// 4. 设置通道超时,防止发送阻塞
|
||||
// 5. 内嵌图片 + HTML 邮件
|
||||
// 6. 用 FromMap 从 map 快捷构建消息
|
||||
// 7. 按错误类型精确处理(errors.Is)
|
||||
//
|
||||
// 怎么运行:设置好下方配置后执行:go run main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"code.yun.ink/pkg/mailx"
|
||||
"code.yun.ink/pkg/mailx/smtp"
|
||||
)
|
||||
|
||||
// appLogger 一个简单的 Logger 实现(也可接入 zap/logrus)。
|
||||
// 生产环境建议实现 Debugf/Infof/Warnf/Errorf 四个方法,用于观测发送过程。
|
||||
type appLogger struct{}
|
||||
|
||||
func (appLogger) Debugf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[debug] "+format, args...)
|
||||
}
|
||||
func (appLogger) Infof(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[info] "+format, args...)
|
||||
}
|
||||
func (appLogger) Warnf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[warn] "+format, args...)
|
||||
}
|
||||
func (appLogger) Errorf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[error] "+format, args...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
mgr := mailx.NewManager()
|
||||
mgr.SetLogger(appLogger{}) // 注入全局 Logger
|
||||
|
||||
// ===== 1. 同一通道类型注册多份配置(主备切换) =====
|
||||
_ = mgr.RegisterNamed("smtp-main", smtp.New(smtp.Config{
|
||||
Host: "smtp.qq.com", Port: 465, User: "a@qq.com", Password: "main-code",
|
||||
Timeout: 10 * time.Second, // ===== 4. 通道超时,防止发送阻塞 =====
|
||||
}))
|
||||
_ = mgr.RegisterNamed("smtp-backup", smtp.New(smtp.Config{
|
||||
Host: "smtp.163.com", Port: 465, User: "a@163.com", Password: "backup-code",
|
||||
Timeout: 10 * time.Second,
|
||||
}))
|
||||
_ = mgr.SetDefault("smtp-main") // 默认用主通道
|
||||
|
||||
// ===== 2. 遍历当前注册的通道实例信息 =====
|
||||
for _, info := range mgr.Senders() {
|
||||
fmt.Printf("sender instance: name=%s type=%s\n", info.Name, info.Type)
|
||||
}
|
||||
|
||||
// ===== 5. 内嵌图片 + HTML 邮件 =====
|
||||
msg := mailx.NewMessage().
|
||||
From(`"通知中心" <a@qq.com>`). // 发件人带显示名
|
||||
To("user@example.com").
|
||||
Subject("生产环境告警").
|
||||
Text("这是一封告警邮件,请及时处理。").
|
||||
HTML(`<h2>磁盘使用率超过 90%</h2><img src="cid:chart1">`).
|
||||
InlineImageBytes("chart1", "chart.png", []byte{0x89, 0x50, 0x4e, 0x47}). // 内嵌图片,cid:chart1 对应
|
||||
Header("X-Mailer", "mailx"). // 自定义邮件头
|
||||
Header("List-Unsubscribe", "<https://example.com/unsub>"). // 退订头(营销邮件合规)
|
||||
Build()
|
||||
|
||||
// ===== 3. 用默认通道发送(自动注入 Logger) =====
|
||||
if err := mgr.Send(ctx, msg); err != nil {
|
||||
fmt.Println("main send failed:", err)
|
||||
}
|
||||
|
||||
// ===== 7. 错误类型精确处理:主通道失败自动切换备通道 =====
|
||||
err := mgr.SendWith(ctx, "smtp-main", msg)
|
||||
if err != nil {
|
||||
// 发送失败(如网络/认证)属于 ErrSendFailed,而非配置/消息问题
|
||||
if errors.Is(err, mailx.ErrSendFailed) {
|
||||
fmt.Println("main failed, switching to backup:", err)
|
||||
if err2 := mgr.SendWith(ctx, "smtp-backup", msg); err2 != nil {
|
||||
fmt.Println("backup send failed:", err2)
|
||||
return
|
||||
}
|
||||
} else if errors.Is(err, mailx.ErrInvalidMessage) {
|
||||
fmt.Println("message invalid:", err) // 消息本身有问题,重试无意义
|
||||
return
|
||||
} else {
|
||||
fmt.Println("send error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 6. 用 FromMap 从 map 快捷构建消息(适合接入 HTTP 请求体) =====
|
||||
m, err := mailx.FromMap(map[string]any{
|
||||
"from": "a@qq.com",
|
||||
"to": "user@example.com, admin@example.com",
|
||||
"subject": "welcome",
|
||||
"text": "hi",
|
||||
"html": "<b>welcome</b>",
|
||||
})
|
||||
if err == nil {
|
||||
_ = mgr.Send(ctx, m)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// basic 展示单个通道直接发送的完整用法。
|
||||
//
|
||||
// 功能:用 SMTP 发送一封含 HTML 正文、抄送、附件、内嵌图片的邮件。
|
||||
//
|
||||
// 怎么运行:
|
||||
// 1. 把下方 smtp.Config 的 4 个配置改成你自己的
|
||||
// 2. 在项目根目录执行:go run ./examples/basic
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.yun.ink/pkg/mailx"
|
||||
"code.yun.ink/pkg/mailx/smtp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// ===== 第 1 步:创建 SMTP 通道 =====
|
||||
client := smtp.New(smtp.Config{
|
||||
Host: "smtp.qq.com", // SMTP 服务器地址
|
||||
Port: 587, // 端口:465=SSL,587/25=STARTTLS
|
||||
User: "sender@qq.com", // 账号
|
||||
Password: "your-auth-code", // 授权码(非邮箱密码)
|
||||
From: "sender@qq.com", // 默认发件人(可选,Message.From 未设置时使用)
|
||||
ReplyTo: "sender@qq.com", // 默认回复地址(可选)
|
||||
})
|
||||
|
||||
// ===== 第 2 步:构建消息 =====
|
||||
// 地址都可带显示名,如 "张三" <a@qq.com>;收件人/抄送/密送可传多个
|
||||
msg := mailx.NewMessage().
|
||||
From(`"客服中心" <sender@qq.com>`). // 发件人(带显示名)
|
||||
To("receiver@example.com"). // 收件人
|
||||
Cc("manager@example.com"). // 抄送(可选)
|
||||
Bcc("leader@example.com"). // 密送(可选)
|
||||
Subject("hello from mailx").
|
||||
Text("如果邮件客户端不支持 HTML,会显示这段纯文本。"). // 纯文本正文(推荐)
|
||||
HTML(`<h1>Hello</h1><p>This email is sent by mailx.</p><img src="cid:logo1">`).
|
||||
ReplyTo("support@example.com"). // 回复地址(可选,覆盖 Config.ReplyTo)
|
||||
Attach("report.txt"). // 按路径添加附件(路径需真实存在)
|
||||
AttachBytes("summary.txt", []byte("summary content")). // 内存附件
|
||||
InlineImageBytes("logo1", "logo.png", mustPNG()). // 内嵌图片(HTML 中 src="cid:logo1")
|
||||
Build()
|
||||
|
||||
// ===== 第 3 步:发送 =====
|
||||
if err := client.Send(ctx, msg); err != nil {
|
||||
fmt.Println("send failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("send success")
|
||||
}
|
||||
|
||||
// mustPNG 返回一个极小的 1x1 PNG(1 像素透明图)用于演示内嵌图片。
|
||||
// 实际使用中请替换为真实的图片文件路径或字节内容。
|
||||
func mustPNG() []byte {
|
||||
// 一个合法的 1x1 透明 PNG
|
||||
return []byte{
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00,
|
||||
0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49,
|
||||
0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// custom_sender 展示如何接入一个全新的发送通道(自定义 Sender)。
|
||||
//
|
||||
// 适用场景:公司内部自研邮件网关、短信通道、钉钉/飞书通知等,
|
||||
// 只需实现 mailx.Sender 接口(Name + Send),即可复用 Manager 的
|
||||
// 注册路由、默认通道、日志注入、消息校验等全部能力。
|
||||
//
|
||||
// 怎么运行:在项目根目录执行:go run ./examples/custom_sender
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.yun.ink/pkg/mailx"
|
||||
)
|
||||
|
||||
// printSender 自定义通道:仅把消息打印到控制台,不真正发送。
|
||||
// 只需实现 Sender 接口的两个方法:Name() 和 Send()。
|
||||
type printSender struct {
|
||||
name string // 通道类型名
|
||||
}
|
||||
|
||||
func newPrintSender(name string) *printSender { return &printSender{name: name} }
|
||||
|
||||
// Name 返回通道类型名(如 "print"),用于 Manager 注册与路由
|
||||
func (p *printSender) Name() string { return p.name }
|
||||
|
||||
// Send 实现发送逻辑。ctx 里可通过 mailx.LoggerFromContext 拿到注入的日志器。
|
||||
func (p *printSender) Send(ctx context.Context, msg *mailx.Message) error {
|
||||
logger := mailx.LoggerFromContext(ctx) // 读取 Manager 注入的 Logger(可选)
|
||||
logger.Infof(ctx, "printSender Send called")
|
||||
fmt.Printf("[%s] to=%v cc=%v subject=%q text=%q html=%q\n",
|
||||
p.name, msg.To, msg.Cc, msg.Subject, msg.TextBody, msg.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
// appLogger 实现 mailx.Logger 接口(4 个方法)。
|
||||
// 生产中可改用 zap / logrus / slog 等,只需实现同样 4 个方法即可接入。
|
||||
type appLogger struct{}
|
||||
|
||||
func (appLogger) Debugf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[debug] "+format, args...)
|
||||
}
|
||||
func (appLogger) Infof(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[info] "+format, args...)
|
||||
}
|
||||
func (appLogger) Warnf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[warn] "+format, args...)
|
||||
}
|
||||
func (appLogger) Errorf(_ context.Context, format string, args ...any) {
|
||||
log.Printf("[error] "+format, args...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
mgr := mailx.NewManager()
|
||||
mgr.SetLogger(appLogger{}) // 注入 Logger,自定义通道内可读取
|
||||
|
||||
// 注册自定义通道
|
||||
_ = mgr.Register(newPrintSender("print"))
|
||||
|
||||
// 同一自定义类型也可注册多份实例(不同配置)
|
||||
_ = mgr.RegisterNamed("print-debug", newPrintSender("print"))
|
||||
|
||||
msg := mailx.NewMessage().
|
||||
To("user@example.com").
|
||||
Subject("hello").
|
||||
Text("from custom sender").
|
||||
HTML("<b>from custom sender</b>").
|
||||
Build()
|
||||
|
||||
// 通过 Manager 统一发送
|
||||
if err := mgr.Send(ctx, msg); err != nil {
|
||||
fmt.Println("send failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("send ok")
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// manager 展示多通道管理器的完整用法。
|
||||
//
|
||||
// 功能:
|
||||
// 1. 注册多个不同类型的通道(smtp/aliyun/aws),统一路由
|
||||
// 2. 同一类型注册多份配置(主备 SMTP 切换)
|
||||
// 3. 发送时按实例名指定通道,或发送时临时指定配置
|
||||
// 4. 遍历当前注册的通道实例信息
|
||||
//
|
||||
// 怎么运行:
|
||||
// 1. 替换下方各通道的凭据为真实值
|
||||
// 2. 在项目根目录执行:go run ./examples/manager
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.yun.ink/pkg/mailx"
|
||||
"code.yun.ink/pkg/mailx/aliyun"
|
||||
"code.yun.ink/pkg/mailx/aws"
|
||||
"code.yun.ink/pkg/mailx/smtp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
mgr := mailx.NewManager()
|
||||
|
||||
// ===== 注册多个不同类型的通道 =====
|
||||
// 用 Register 注册时,实例名 = 通道类型名(smtp / aliyun / aws / mailgun)
|
||||
_ = mgr.Register(smtp.New(smtp.Config{
|
||||
Host: "smtp.qq.com", Port: 587, User: "a@qq.com", Password: "code",
|
||||
}))
|
||||
_ = mgr.Register(aliyun.New(aliyun.Config{
|
||||
AccessKeyID: "ak", AccessKeySecret: "sk", AccountName: "noreply@example.com",
|
||||
}))
|
||||
_ = mgr.Register(aws.New(aws.Config{
|
||||
AccessKeyID: "ak", AccessKeySecret: "sk", Region: "ap-northeast-1",
|
||||
Sender: "noreply@example.com",
|
||||
}))
|
||||
|
||||
// ===== 同一类型注册多份配置(主备切换) =====
|
||||
// 用 RegisterNamed 指定实例名,可注册多个 smtp 实例
|
||||
_ = mgr.RegisterNamed("smtp-main", smtp.New(smtp.Config{
|
||||
Host: "smtp.qq.com", Port: 465, User: "a@qq.com", Password: "main-code",
|
||||
}))
|
||||
_ = mgr.RegisterNamed("smtp-backup", smtp.New(smtp.Config{
|
||||
Host: "smtp.163.com", Port: 465, User: "a@163.com", Password: "backup-code",
|
||||
}))
|
||||
|
||||
// ===== 设置默认通道(不设置则用第一个注册的) =====
|
||||
_ = mgr.SetDefault("aliyun")
|
||||
|
||||
// ===== 遍历当前注册的通道实例信息 =====
|
||||
fmt.Println("registered instances:")
|
||||
for _, info := range mgr.Senders() {
|
||||
fmt.Printf(" - %s (type: %s)\n", info.Name, info.Type)
|
||||
}
|
||||
fmt.Println("default:", mgr.Default())
|
||||
|
||||
msg := mailx.NewMessage().
|
||||
From("noreply@example.com").
|
||||
To("user@example.com").
|
||||
Subject("Notice").
|
||||
Text("hello").
|
||||
HTML("<h1>Hello</h1>").
|
||||
Build()
|
||||
|
||||
// ===== 用默认通道发送 =====
|
||||
if err := mgr.Send(ctx, msg); err != nil {
|
||||
fmt.Println("default send:", err)
|
||||
}
|
||||
|
||||
// ===== 按实例名指定通道发送 =====
|
||||
if err := mgr.SendWith(ctx, "aws", msg); err != nil {
|
||||
fmt.Println("aws send:", err)
|
||||
}
|
||||
if err := mgr.SendWith(ctx, "smtp-backup", msg); err != nil { // 主 SMTP 失败时可切备用
|
||||
fmt.Println("smtp-backup send:", err)
|
||||
}
|
||||
|
||||
// ===== 发送时临时指定配置,无需提前注册 =====
|
||||
if err := mgr.SendBy(ctx, smtp.New(smtp.Config{
|
||||
Host: "smtp.163.com", Port: 465, User: "b@163.com", Password: "code",
|
||||
}), msg); err != nil {
|
||||
fmt.Println("temp smtp send:", err)
|
||||
}
|
||||
|
||||
// ===== 注销一个实例(可选) =====
|
||||
mgr.Unregister("smtp-main")
|
||||
fmt.Println("after unregister, instances:", mgr.Names())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// quickstart 是最简单的 mailx 入门示例。
|
||||
//
|
||||
// 功能:用 QQ 邮箱 SMTP 发送一封纯文本邮件。
|
||||
//
|
||||
// 怎么运行:
|
||||
// 1. 在 QQ 邮箱「设置 -> 账户」中开启 SMTP,获取授权码(不是 QQ 密码)
|
||||
// 2. 把下方 main.go 里的 4 个配置改成你自己的
|
||||
// 3. 在本目录执行:go run main.go
|
||||
//
|
||||
// 参考:https://mail.qq.com 开启 SMTP 服务的授权码获取方法
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.yun.ink/pkg/mailx" // mailx 核心包:消息构建 + Sender 接口
|
||||
"code.yun.ink/pkg/mailx/smtp" // smtp 通道:基于 SMTP 协议发送
|
||||
)
|
||||
|
||||
func main() {
|
||||
// context 用于控制发送的超时与取消,先创建空背景 context
|
||||
ctx := context.Background()
|
||||
|
||||
// ===== 第 1 步:创建发送通道 =====
|
||||
// smtp.New 接收一个 smtp.Config 配置,返回一个发送通道实例
|
||||
client := smtp.New(smtp.Config{
|
||||
Host: "smtp.qq.com", // SMTP 服务器地址
|
||||
Port: 587, // 端口:465 走 SSL,587 走 STARTTLS
|
||||
User: "sender@qq.com", // 你的 QQ 邮箱地址
|
||||
Password: "your-auth-code", // 授权码(QQ 邮箱后台开启 SMTP 后获取)
|
||||
// From: "sender@qq.com", // 可选:默认发件人,不填则用上面的 User
|
||||
})
|
||||
|
||||
// ===== 第 2 步:构建邮件消息 =====
|
||||
// 使用链式调用(builder 模式),一行一个字段,最后 Build() 生成消息
|
||||
msg := mailx.NewMessage().
|
||||
From("sender@qq.com"). // 发件人(可带显示名,如 "张三" <a@qq.com>)
|
||||
To("receiver@example.com"). // 收件人,可传多个:To("a@x.com", "b@x.com")
|
||||
Subject("hello from mailx"). // 邮件主题
|
||||
Text("This is a plain text email."). // 纯文本正文
|
||||
Build() // 生成 *Message
|
||||
|
||||
// ===== 第 3 步:发送 =====
|
||||
// 传入 ctx 和消息,返回 error(nil 表示成功)
|
||||
if err := client.Send(ctx, msg); err != nil {
|
||||
fmt.Println("send failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("send success")
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# 复制本文件为 .env 并填入你自己的真实凭据
|
||||
# 注意:.env 包含敏感信息,不要提交到 git(已在 .gitignore 中忽略)
|
||||
|
||||
# ===== 收件人(必填) =====
|
||||
TO_ADDR=receiver@example.com
|
||||
# 发件人兜底地址(选填)
|
||||
FROM_ADDR=sender@example.com
|
||||
|
||||
# ===== SMTP 通道(选填,配置后即启用) =====
|
||||
SMTP_HOST=smtp.qq.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=sender@qq.com
|
||||
SMTP_PASSWORD=your-auth-code
|
||||
SMTP_FROM=sender@qq.com
|
||||
|
||||
# ===== 阿里云邮件推送(选填) =====
|
||||
ALIYUN_ACCESS_KEY_ID=your-access-key-id
|
||||
ALIYUN_ACCESS_KEY_SECRET=your-access-key-secret
|
||||
ALIYUN_ACCOUNT_NAME=noreply@example.com
|
||||
# ALIYUN_ENDPOINT=dm.aliyuncs.com
|
||||
|
||||
# ===== AWS SES(选填) =====
|
||||
AWS_ACCESS_KEY_ID=your-aws-ak
|
||||
AWS_ACCESS_KEY_SECRET=your-aws-sk
|
||||
AWS_REGION=ap-northeast-1
|
||||
AWS_SENDER=noreply@example.com
|
||||
|
||||
# ===== Mailgun(选填) =====
|
||||
MAILGUN_API_KEY=your-api-key
|
||||
MAILGUN_DOMAIN=mg.example.com
|
||||
MAILGUN_SENDER=noreply@example.com
|
||||
@@ -0,0 +1,127 @@
|
||||
// with_env 展示如何通过环境变量管理通道凭据(避免把密钥写死在代码里)。
|
||||
//
|
||||
// 适用场景:已有多个通道的凭据(SMTP / 阿里云 / AWS SES / Mailgun),
|
||||
// 希望在程序启动时从环境变量读取配置,注册到 Manager 统一管理。
|
||||
//
|
||||
// 怎么运行:
|
||||
// 1. 复制 .env.example 为 .env(或直接设置下方环境变量)
|
||||
// 2. 安装 dotenv 依赖:go get github.com/joho/godotenv
|
||||
// 3. 设置好至少一个通道的凭据后执行:go run main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"code.yun.ink/pkg/mailx"
|
||||
"code.yun.ink/pkg/mailx/aliyun"
|
||||
"code.yun.ink/pkg/mailx/aws"
|
||||
"code.yun.ink/pkg/mailx/mailgun"
|
||||
"code.yun.ink/pkg/mailx/smtp"
|
||||
|
||||
"github.com/joho/godotenv" // 读取 .env 文件(可选)
|
||||
)
|
||||
|
||||
// buildSenders 从环境变量读取凭据,返回已配置好的通道列表。
|
||||
// 只返回凭据齐全的通道,未配置的通道直接跳过,方便按需启用。
|
||||
func buildSenders() []mailx.Sender {
|
||||
var senders []mailx.Sender
|
||||
|
||||
// --- SMTP ---
|
||||
if os.Getenv("SMTP_HOST") != "" {
|
||||
senders = append(senders, smtp.New(smtp.Config{
|
||||
Host: os.Getenv("SMTP_HOST"),
|
||||
Port: atoi(os.Getenv("SMTP_PORT"), 465),
|
||||
User: os.Getenv("SMTP_USER"),
|
||||
Password: os.Getenv("SMTP_PASSWORD"),
|
||||
From: os.Getenv("SMTP_FROM"),
|
||||
}))
|
||||
}
|
||||
|
||||
// --- 阿里云邮件推送 ---
|
||||
if os.Getenv("ALIYUN_ACCESS_KEY_ID") != "" {
|
||||
senders = append(senders, aliyun.New(aliyun.Config{
|
||||
AccessKeyID: os.Getenv("ALIYUN_ACCESS_KEY_ID"),
|
||||
AccessKeySecret: os.Getenv("ALIYUN_ACCESS_KEY_SECRET"),
|
||||
AccountName: os.Getenv("ALIYUN_ACCOUNT_NAME"),
|
||||
Endpoint: os.Getenv("ALIYUN_ENDPOINT"), // 可选,默认 dm.aliyuncs.com
|
||||
}))
|
||||
}
|
||||
|
||||
// --- AWS SES ---
|
||||
if os.Getenv("AWS_ACCESS_KEY_ID") != "" {
|
||||
senders = append(senders, aws.New(aws.Config{
|
||||
AccessKeyID: os.Getenv("AWS_ACCESS_KEY_ID"),
|
||||
AccessKeySecret: os.Getenv("AWS_ACCESS_KEY_SECRET"),
|
||||
Region: os.Getenv("AWS_REGION"),
|
||||
Sender: os.Getenv("AWS_SENDER"),
|
||||
}))
|
||||
}
|
||||
|
||||
// --- Mailgun ---
|
||||
if os.Getenv("MAILGUN_API_KEY") != "" {
|
||||
senders = append(senders, mailgun.New(mailgun.Config{
|
||||
APIKey: os.Getenv("MAILGUN_API_KEY"),
|
||||
Domain: os.Getenv("MAILGUN_DOMAIN"),
|
||||
Sender: os.Getenv("MAILGUN_SENDER"),
|
||||
}))
|
||||
}
|
||||
|
||||
return senders
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 可选:加载 .env 文件(没有 .env 时静默忽略,改用系统环境变量)
|
||||
_ = godotenv.Load()
|
||||
|
||||
senders := buildSenders()
|
||||
if len(senders) == 0 {
|
||||
log.Fatal("no channel configured, please set env vars (see .env.example)")
|
||||
}
|
||||
|
||||
// 注册到 Manager,第一个注册的成为默认通道
|
||||
mgr := mailx.NewManager()
|
||||
for _, s := range senders {
|
||||
if err := mgr.Register(s); err != nil {
|
||||
log.Fatalf("register %s: %v", s.Name(), err)
|
||||
}
|
||||
}
|
||||
log.Printf("registered channels: %v (default: %s)", mgr.Names(), mgr.Default())
|
||||
|
||||
msg := mailx.NewMessage().
|
||||
From(firstNonEmpty(os.Getenv("FROM_ADDR"), os.Getenv("SMTP_FROM"), os.Getenv("AWS_SENDER"), os.Getenv("MAILGUN_SENDER"))).
|
||||
To(os.Getenv("TO_ADDR")).
|
||||
Subject("hello from mailx (env config)").
|
||||
Text("This email is sent using env-var configured channel.").
|
||||
Build()
|
||||
|
||||
// 用默认通道发送
|
||||
if err := mgr.Send(ctx, msg); err != nil {
|
||||
fmt.Println("send failed:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("send success")
|
||||
}
|
||||
|
||||
// atoi 解析端口,失败时返回默认值
|
||||
func atoi(s string, def int) int {
|
||||
n := 0
|
||||
if _, err := fmt.Sscanf(s, "%d", &n); err != nil || n <= 0 {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// firstNonEmpty 返回第一个非空字符串
|
||||
func firstNonEmpty(vals ...string) string {
|
||||
for _, v := range vals {
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user