50.Go实现邮件发送

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/30-go-email

一、简介

程序中时常有发送通知的需求。有异常情况了需要通知管理员和负责人,用户下单后可能需要通知订单信息,电商平台、中国移动和联通都有每月账单,这些都可以通过飞书、微信、钉钉、短信或者邮件来推送。之前我们已经介绍过如果发送飞书消息 7. 飞书机器人发送交互式信息,本次我们介绍下如何发送邮件,其实我们平时收到的垃圾邮件大都也是通过这种方式发送的。那么如何在 Go 语言发送邮件?本文我们介绍一下email库的使用。

二、使用

1、前置准备

首先,你需要在程序中导入库:

import "github.com/jordan-wright/email"

该库文档地址:https://pkg.go.dev/github.com/jordan-wright/email
该库github地址:https://github.com/darjun/go-daily-lib

可以先贴一下Email有哪些字段,后面发送邮件就是在设置这些字段的值,然后e.Send(...)发送

// Email is the type used for email messages
type Email struct {
	ReplyTo     []string
	From        string  // 发送人
	To          []string  // 接收人,可多个
	Bcc         []string // 秘密抄送人,可多个
	Cc          []string // 抄送人,可多个
	Subject     string   // 主题(标题)
	Text        []byte // Plaintext message (optional) 文本内容,可选
	HTML        []byte // Html message (optional)  HTML内容,可选
	Sender      string // override From as SMTP envelope sender (optional)
	Headers     textproto.MIMEHeader
	Attachments []*Attachment
	ReadReceipt []string
}

进入开发前我们需要做一些工准备工作。我们知道邮箱使用SMTP/POP3/IMAP等协议从邮件服务器上拉取邮件。邮件并不是直接发送到邮箱的,而是邮箱请求拉取的。 所以,我们需要配置SMTP/POP3/IMAP服务器。从头搭建固然可行,而且也有现成的开源库,但是比较麻烦。现在一般的邮箱服务商都开放了SMTP/POP3/IMAP服务器。 我这里拿 QQ 邮箱来举例,使用SMTP服务器。当然,用其他邮箱也可以。

  • 首先,登录邮箱;

  • 点开顶部的设置,选择POP3/SMTP/IMAP;
    在这里插入图片描述

  • 点击开启IMAP/SMTP服务,按照步骤开启即可,有个授权码获取,记住这个授权码,后面有用。
    在这里插入图片描述

2、简单使用

package main

import (
	"github.com/jordan-wright/email"
	"log"
	"net/smtp"
)

func main() {
	e := email.NewEmail()
	e.From = "lym <1154041111@qq.com>"
	e.To = []string{"15074941111@163.com"}
	e.Subject = "【测试】QQ邮箱给163邮箱发送邮件"
	e.Text = []byte("测试邮件,收到可以忽略")
	err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
	if err != nil {
		log.Fatal(err)
	}
}

这里为了我的信息安全,我把真实信息都隐藏了。代码中邮箱的后四位我都替换成了1111,大家可以替换成你的邮箱账号,yyy替换成上面获取的授权码。

代码步骤比较简单清晰:

  • 先调用NewEmail创建一封邮件;
  • 设置From发送方,To接收者,Subject邮件主题(标题),Text设置邮件内容;
  • 然后调用Send发送,参数1SMTP 服务器的地址,格式为host:port,参数2为验证信息,smtp.PlainAuth函数用于创建一个普通的Auth(认证)对象,参数为SMTP服务器要求的身份验证信息。

注意端口号用465如果发送不成功时,换成587
在这里插入图片描述

运行程序我的QQ邮箱将会向我的 163 邮箱发送一封邮件:

**这里插入发送成功的图片**

有的邮箱会把这种邮件放在垃圾箱中,例如 QQ。如果收件箱找不到,记得到垃圾箱瞅瞅。

3、抄送

平常我们发邮件的时候可能会抄送给一些人,还有一些人要秘密抄送,即 CC(Carbon Copy) BCC (Blind Carbon Copy)email我们也可以设置这两个参数:

func testCarbonCopy() {
	e := email.NewEmail()
	e.From = "lym <1154041111@qq.com>"
	e.To = []string{"15074941111@163.com"}
	// 主要就是在这里添加了抄送人以及秘密抄送人
	e.Cc = []string{"1315381111@qq.com"}
	e.Bcc = []string{"1315381111@qq.com"}
	e.Subject = "【测试】QQ邮箱发送邮件,并抄送和秘密抄送发送"
	e.Text = []byte("测试抄送和密码抄送邮件,收到可以忽略")
	err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	testCarbonCopy()
}

同样,代码中后四位我都换成了1,大家使用时邮箱自己替换后用自己的。

运行程序将会向我的 163 邮件发送一封邮件,同时抄送一封到我另一个 QQ 邮箱:
在这里插入图片描述

抄送人也收到了邮件

在这里插入图片描述

3、HTML邮件

发送纯文本,邮件不太美观。email支持发送 HTML 格式的内容。与发送纯文本类似,直接设置对象的HTML字段:

func testHTMLEmail() {
	e := email.NewEmail()
	e := email.NewEmail()
	e.From = "lym <1154041111@qq.com>"
	e.To = []string{"15074941111@163.com"}
	e.Cc = []string{"1154041111@qq.com"}
	
	e.Subject = "【测试】发送HTML邮件"
	// 主要就是在这里添加HTML字节数组内容
	e.HTML = []byte(`
  <ul>
<li><a "https://pkg.go.dev/github.com/jordan-wright/email/">Go邮件库github.com/jordan-wright/email文档地址</a></li>
<li><a "https://darjun.github.io/2020/01/10/godailylib/go-flags/">Go邮件库github.com/jordan-wright/email Github地址</a></li>
</ul>
  `)
	err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	testHTMLEmail()
}

注意, SMTP 服务器检测比较严格,加上 HTML 之后,很容易被识别为垃圾邮件不让发送,这时 抄送 自己就 OK 了。

发送结果:
在这里插入图片描述

4、附件

添加附件也很容易,直接调用AttachFile即可:

func testAttachFile() {
	e := email.NewEmail()
	e.From = "lym <1154041111@qq.com>"
	e.To = []string{"15074941111@163.com"}
	e.Subject = "【测试】邮件携带附件"
	e.Text = []byte("测试邮件,请查看是否有附件")

	// 主要就是在这个位置添加了附件,注意:这里AttachFile是Email结构体的方法而不是字段,所以不是用等于而是括号
	e.AttachFile("30-go-email/test.txt")
	err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	testAttachFile()
}

发送结果:
在这里插入图片描述

5、连接池

实际上每次调用Send时都会和 SMTP 服务器建立一次连接,如果发送邮件很多很频繁的话可能会有性能问题。email提供了连接池,可以复用网络连接:

func testEmailPool() {
	ch := make(chan *email.Email, 10)

	// 创建连接池,这里的参数和Send方法的参数几乎一致,不过多了一个连接池数量的参数
	p, err := email.NewPool(
		"smtp.qq.com:587",
		4,
		smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"),
	)

	if err != nil {
		log.Fatal("failed to create pool:", err)
	}

	// 开启四个协程充当消费者,发送邮件
	var wg sync.WaitGroup
	wg.Add(4)
	for i := 0; i < 4; i++ {
		go func() {
			defer wg.Done()
			for e := range ch {
				// 这里的Send是连接池对象的方法,第一个参数是Email对象,第二个参数表示超时时间,这个时间内没有发送成功会报错
				err := p.Send(e, 10*time.Second)
				if err != nil {
					fmt.Fprintf(os.Stderr, "email:%v sent error:%v\n", e, err)
				}
			}
		}()
	}

	for i := 0; i < 10; i++ {
		e := email.NewEmail()
		e.From = "lym <1154041111@qq.com>"
		e.To = []string{"15074941111@163.com"}
		e.Subject = fmt.Sprintf("测试连接池:%d", i+1)
		e.Text = []byte(fmt.Sprintf("测试连接池:%d", i+1))
		ch <- e
	}

	close(ch) // 发送完毕后,关闭通道
	wg.Wait() // 通道关闭后,ch中消费完后,4个消费者会退出协程,从而放行
}

func main() {
	testEmailPool()
}

上面程序中,我们创建 4 goroutine 共用一个连接池发送邮件,发送 10 封邮件后程序退出。为了等邮件都发送完成或失败,程序才退出,我们使用了sync.WaitGroup

发送结果,邮箱被轰炸了:

由于使用了多个 goroutine消费同一个channel,邮件顺序不能保证。
在这里插入图片描述

<think>好的,用户现在问的是如何在Golang实现邮件发送。之前他们问过GORM的自动重连,可能是在开发一个需要数据库和邮件通知的后端服务。需要先理清Golang发送邮件的常见方法,可能需要支持SMTP或者第三方API。用户可能是想集成邮件功能到现有的Go项目中,比如用户注册、通知等功能。 首先,用户可能需要基本的SMTP发送示例,这是最直接的方式。要确保示例代码正确,包括认证、设置邮件头、附件处理等。同时,可能需要提到第三方库如gomail,这能简化操作,特别是处理附件和HTML内容的时候。 然后,用户可能关心如何发送HTML邮件和附件,因为这是常见需求。需要分点说明,用代码示例展示不同的情况。另外,错误处理也很重要,需要提醒用户检查发送过程中的错误,避免程序崩溃。 接下来,第三方服务如SendGrid和Mailgun可能对用户有用,特别是如果用户不想自己维护SMTP服务器的话。需要给出简单的示例,并说明注册获取API密钥的步骤。 安全性方面,用户可能不知道如何避免敏感信息泄露,比如不要硬编码密码,使用环境变量。同时,连接加密(TLS)也是必须强调的点。 异步发送和超时控制对于性能敏感的应用很重要,用户可能希望邮件发送不阻塞主线程。这里需要用goroutine和context来示例说明。 最后,最佳实践部分需要总结关键点,比如参数配置、模板引擎、错误重试、监控等。用户可能需要这些建议来优化邮件发送的可靠性和效率。 需要检查是否有遗漏的部分,比如邮件模板的使用,或者如何处理大量的邮件发送。另外,确保所有代码示例都是正确且符合最新Golang版本的语法。可能还需要提到测试,比如如何模拟邮件发送进行单元测试,但用户可能暂时不需要,所以放在后面的注意事项里。 最后,整理结构,确保步骤清晰,从基础到进阶,再到第三方服务,最后是安全和优化建议。这样用户可以从简单开始,逐步深入,满足不同层次的需求。</think>以下是用 Go 语言实现邮件发送的完整解决方案,包含多种实现方式和最佳实践: 📧 基础 SMTP 发送实现 ```go package main import ( "net/smtp" "strings" ) func SendSMTPMail(to []string, subject, body string) error { // 配置信息(建议使用环境变量) from := "your_email@example.com" password := "your_password" smtpHost := "smtp.example.com" smtpPort := "587" // 认证信息 auth := smtp.PlainAuth("", from, password, smtpHost) // 构建邮件内容 msg := []byte( "To: " + strings.Join(to, ",") + "\r\n" + "Subject: " + subject + "\r\n" + "MIME-version: 1.0;\r\nContent-Type: text/html; charset=\"UTF-8\";\r\n\r\n" + body, ) // 发送邮件 return smtp.SendMail( smtpHost+":"+smtpPort, auth, from, to, msg, ) } ``` 📎 带附件的实现(推荐使用第三方库) ```go // 使用 go get github.com/go-gomail/gomail func SendWithAttachment() { m := gomail.NewMessage() m.SetHeader("From", "sender@example.com") m.SetHeader("To", "recipient@example.com") m.SetHeader("Subject", "带附件的邮件") m.SetBody("text/html", "<b>Hello</b> 这是一封测试邮件") m.Attach("/path/to/file.pdf") // 添加附件 d := gomail.NewDialer("smtp.example.com", 587, "user", "password") if err := d.DialAndSend(m); err != nil { panic(err) } } ``` 🔐 安全配置建议 1. TLS 加密连接: ```go // 在基础实现中添加 TLS 配置 c, err := smtp.Dial(smtpHost + ":" + smtpPort) if err != nil { return err } defer c.Close() if ok, _ := c.Extension("STARTTLS"); ok { config := &tls.Config{ServerName: smtpHost} if err = c.StartTLS(config); err != nil { return err } } ``` 2. 环境变量配置: ```bash # .env 文件示例 SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=your_email@example.com SMTP_PASSWORD=your_app_specific_password ``` 📨 使用第三方邮件服务(以 SendGrid 为例) ```go // 安装 SDK: go get github.com/sendgrid/sendgrid-go func SendViaSendGrid() { from := mail.NewEmail("发件人", "sender@example.com") to := mail.NewEmail("收件人", "recipient@example.com") content := mail.NewContent("text/html", "<strong>Hello</strong> 邮件内容") message := mail.NewV3MailInit(from, "邮件主题", to, content) client := sendgrid.NewSendClient("YOUR_SENDGRID_API_KEY") response, err := client.Send(message) if err != nil { log.Println(err) } else { fmt.Println(response.StatusCode) } } ``` ⚡ 异步发送实现 ```go func AsyncSendMail(emailCh <-chan *EmailRequest) { go func() { for req := range emailCh { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 带超时控制的发送 done := make(chan error) go func() { done <- SendSMTPMail(req.To, req.Subject, req.Body) }() select { case <-ctx.Done(): log.Println("邮件发送超时") case err := <-done: if err != nil { log.Println("发送失败:", err) } } } }() } ``` 📝 最佳实践建议 1. 邮件模板处理: ```go // 使用 text/template 或 html/template var emailTemplate = template.Must(template.New("email").Parse(` <html> <body> <h1>Hello {{.Name}}</h1> <p>您的验证码是:{{.Code}}</p> </body> </html> `)) func GenerateContent(data interface{}) (string, error) { var buf bytes.Buffer if err := emailTemplate.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil } ``` 2. 错误重试机制: ```go func SendWithRetry(to []string, subject, body string) error { maxRetries := 3 for i := 0; i < maxRetries; i++ { err := SendSMTPMail(to, subject, body) if err == nil { return nil } time.Sleep(time.Duration(i+1)*2 * time.Second) // 指数退避 } return errors.New("邮件发送失败") } ``` 3. 服务监控: ```go // Prometheus 监控示例 var ( emailsSent = promauto.NewCounter(prometheus.CounterOpts{ Name: "app_emails_sent_total", Help: "已发送邮件总数", }) emailErrors = promauto.NewCounter(prometheus.CounterOpts{ Name: "app_email_errors_total", Help: "邮件发送错误次数", }) ) func InstrumentedSendMail() { start := time.Now() defer func() { duration := time.Since(start).Seconds() emailSendDuration.Observe(duration) }() if err := SendSMTPMail(...); err != nil { emailErrors.Inc() } emailsSent.Inc() } ``` 💡 注意事项 1. 敏感信息处理: - 不要将密码硬编码在代码中 - 使用加密的密钥管理系统 - 为邮件服务创建专用账户 2. 反垃圾邮件策略: - 添加退订链接 - 包含物理地址信息 - 控制发送频率 - 使用 DKIM/SPF 验证 3. 性能优化: - 使用连接池 (推荐使用 github.com/emersion/go-smtp 客户端) - 批量发送时合并请求 - 预先生成邮件内容 可以根据具体需求选择标准库方案或第三方库方案。生产环境推荐使用 SendGrid、Mailgun 等专业邮件服务,它们提供: - 发送状态跟踪 - 统计分析 - 弹回处理 - 邮件模板管理 等高级功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值