如何使用 GOLANG 发送邮件

2,741 阅读8分钟

这是我参与更文挑战的第 22 天,活动详情查看: 更文挑战

使用 GOLANG 发送邮件

我们一起来回顾一下上一次咱们说到的GO 的验证码案例

  • 验证码种类梳理
  • 验证码库的安装
  • 验证码库的源码介绍
  • 实操,编码
  • 验证码效果展示

想看看GO 咋做验证码的,欢迎查看文章 GO 的验证码案例

生活和工作中,咱们都离不开邮件的收发,要么在手机上查收和发送,要么在自己的电脑前面进行邮件编辑和处理

可是,我们会发现,很多时候,某类邮件,咱们每天都必须要在同一个时刻发出去,并且内容也是大同小异的,

而且,有时候因为各种各种各样的原因,不能准时的发送邮件,这个时候,咱们如果可以写一个定制化的发送邮件的程序那可以说还是很香的

那么,咱们还是先来看看一些基本的常识吧

邮件是什么?

邮件是指经传递方式处理的文件

邮件进行传递的过程称为邮递,而从事邮递服务的机构或系统,则称为邮政

邮件有国内邮件和国际邮件两类

那么电子邮件又是个啥?

电子邮件是—种用电子手段提供信息交换的通信方式是互联网应用最广的服务

电子邮件的优势是啥?

电子邮件依托于网络的电子邮件系统,有如下优势:

  • 价格非常低廉

不管发送到哪里,都只需负担网费

  • 传输快速

几秒钟之内可以发送到世界上任何指定的目的地,与世界上任何一个角落的网络用户联系

电子邮件的形式是啥样的?

  • 文字
  • 图像
  • 声音等

想一想每次发邮件都需要经历如下的步骤

  • 打开电脑
  • 进入浏览器
  • 打开电子邮件
  • 新建 - 编辑 - 发送

大部分内容还是复制粘贴的,

妥妥的一个工具人,好滴,现在就来看看 使用 GOLang 咋发邮件

邮件协议

咱们使用编程语言需要遵守编程语言的规范,我们在浏览器里面浏览网页也是一样,需要遵循各种网络协议

那么,我们发送邮件的必须也要先知道有哪些邮件协议可以使用,咱们都来列举一下

  • SMTP

SMTP是 简单邮件传输协议,是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式

另外 SMTP 协议属于TCP/IP协议簇

  • POP3

邮局协议的第3个版本,是因特网电子邮件的第一个离线协议标准

  • IMAP

是一种优于POP的新协议

POP一样,IMAP也能下载邮件、从服务器中删除邮件或询问是否有新邮件

IMAP可让用户在服务器上创建并管理邮件文件夹或邮箱、删除邮件、查询某封信的一部分或全部内容

最终完成所有这些工作都不需要把邮件从服务器下载到用户的个人计算机上

OK,让我们开始进入到编码环节

开始编码发邮件

咱们今天就使用 SMTP 协议来发送邮件,有如下几个步骤

  • 在 QQ 邮箱上面,拿到授权码
  • 编码,并安装email 邮件库
  • 开始发送邮件

QQ 邮箱上面,拿到授权码

  • 进入 QQ邮箱,点击 设置 -> 账户

  • 下滑页面,看到 POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务

  • 开启 POP3/SMTP服务
  • 开启 IMAP/SMTP服务
  • 生成授权码,这个授权码自己找地方保存起来

编码,并安装email 邮件库

咱们本次写的邮件小案例,用到的GO 的包是 "github.com/jordan-wright/email" , 我们可以这样来手动安装

 go get github.com/jordan-wright/email

不进行手动安装也没有问题,咱们可以现在看看 写一个初版的发送邮件小案例

package main

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

func main() {
   // 简单设置 log 参数
   log.SetFlags(log.Lshortfile | log.LstdFlags)

   em := email.NewEmail()
   // 设置 sender 发送方 的邮箱 , 此处可以填写自己的邮箱
   em.From = "xx <xxx@qq.com>"
    
   // 设置 receiver 接收方 的邮箱  此处也可以填写自己的邮箱, 就是自己发邮件给自己
   em.To = []string{"xxx@qq.com"}
    
   // 设置主题
   em.Subject = "小魔童给你发邮件了"
    
   // 简单设置文件发送的内容,暂时设置成纯文本
   em.Text = []byte("hello world, 咱们用 golang 发个邮件!!")
    
   //设置服务器相关的配置
   err := e.Send("smtp.qq.com:25", smtp.PlainAuth("", "自己的邮箱账号", "自己邮箱的授权码", "smtp.qq.com"))
   if err != nil {
      log.Fatal(err)
   }
   log.Println("send successfully ... ")
}

运行上述代码的效果如下:

2021/06/xx xx:36:28 main.go:28: send successfully ...

说明发送邮件成功了,咱们来看看我们的邮箱

果然是发送成功了 , 窃喜

那么今天的文章分享就到这里了吧 ,这时你会问,我想发表格,想发html内容,还想发附件,甚至我想秘密抄送给某些人

好的安排

发送HTML内容 + 附件

咱们可以先来看看 这个库支持的哪些字段,上述NewEmail 方法时候返回一个指针

// NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
   return &Email{Headers: textproto.MIMEHeader{}}
}

这个指针是指向了一个邮件的数据结构,咱们一起来瞅瞅

// 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)
   Sender      string // override From as SMTP envelope sender (optional)
   Headers     textproto.MIMEHeader  // 协议头
   Attachments []*Attachment    // 附件
   ReadReceipt []string
}

看了上述结构,咱们是不是发送邮件的时候,就可以抄送,密送,添加附件,或者发 HTML 的内容了呢 ,咱们改造一下上述的代码

func main() {
   // 简单设置l og 参数
   log.SetFlags(log.Lshortfile | log.LstdFlags)

   em := email.NewEmail()
   //设置发送方的邮箱
   em.From = "xx <xxx@qq.com>"
   // 设置接收方的邮箱
   em.To = []string{"xxx@qq.com"}

   // 抄送
   em.Cc = []string{"xxx@qq.com"}

   // 密送
   em.Bcc = []string{"xxx@qq.com"}

   //设置主题
   em.Subject = "小魔童给你发邮件了"
   //设置文件发送的内容
   em.HTML = []byte(`
   <h3><a href="https://juejin.cn/user/3465271329953806">欢迎来到小魔童哪吒的主页</a></h3>
<br/>
<table border="0" style="font-family: '微软雅黑 Light';" cellpadding="3px">
    <tr>
        <td> 1 </td>
        <td><a href="https://juejin.cn/post/6975686540601245709">GO 中 defer的实现原理</a></td>
    </tr>
    <tr>
        <td> 2 </td>
        <td><a href="https://juejin.cn/post/6975280009082568740">GO 中 Chan 实现原理分享</a></td>
    </tr>
    <tr>
        <td> 3 </td>
        <td><a href="https://juejin.cn/post/6974908615232585764">GO 中 map 的实现原理</a></td>
    </tr>
    <tr>
        <td> 4 </td>
        <td><a href="https://juejin.cn/post/6974539862800072718">GO 中 slice 的实现原理</a></td>
    </tr>
    <tr>
        <td> 5 </td>
        <td> <a href="https://juejin.cn/post/6974169270624190495">GO 中 string 的实现原理</a></td>
    </tr>
    <tr>
        <td> 6 </td>
        <td><a href="https://juejin.cn/post/6973793593987170317">GO 中 ETCD 的编码案例分享</a></td>
    </tr>
    <tr>
        <td> 7 </td>
        <td><a href="https://juejin.cn/post/6973455825905909797">服务注册与发现之ETCD</a></td>
    </tr>
    <tr>
        <td> 8 </td>
        <td> <a href="https://juejin.cn/post/6973108979777929230">GO通道和 sync 包的分享</a></td>
    </tr>
    <tr>
        <td> 9 </td>
        <td> <a href="https://juejin.cn/post/6972846349968474142">GO的锁和原子操作分享</a></td>
    </tr>
</table>
`)
   // 添加附件
   em.AttachFile("./test.html")
   // 设置服务器相关的配置
   err := e.Send("smtp.qq.com:25", smtp.PlainAuth("", "自己的邮箱账号", "自己邮箱的授权码", "smtp.qq.com"))
   if err != nil {
      log.Fatal(err)
   }
   log.Println("send successfully ... ")
}

运行之后,咱们去查收一下邮件

抄送,密送,添加附件,发送 HTML 内容,全部满上

如何提高发送邮件的性能

get github.com/jordan-wright/email 包里面为我们提供了连接池,你就说香不香,咱们可以复用上一次的网络连接来发送邮件,而不是每发送一次邮件,就建立一次连接

我们都知道,建立连接是会耗费时间和资源的,咱们得尽可能的优化

再来一个DEMO,连接池的,咱们运用连接池,创建 一个 有 5 个缓冲的通道,让 3 个协程去通道里面获取数据,然后发送邮件

func main() {

   // 简单设置l og 参数
   log.SetFlags(log.Lshortfile | log.LstdFlags)

   // 创建有5 个缓冲的通道,数据类型是  *email.Email
   ch := make(chan *email.Email, 5)
   // 连接池
   p, err := email.NewPool(
      "smtp.qq.com:25",
      3,	// 数量设置成 3 个
      smtp.PlainAuth("", "自己的邮箱", "自己邮箱的授权码", "smtp.qq.com"),
   )

   if err != nil {
      log.Fatal("email.NewPool error : ", err)
   }
	
   // sync 包,控制同步
   var wg sync.WaitGroup
   wg.Add(3)
   for i := 0; i < 3; i++ {
      go func() {
         defer wg.Done()
          // 若 ch 无数据,则阻塞, 若 ch 关闭,则退出循环
         for e := range ch {
            // 超时时间 10 秒
            err := p.Send(e, 10*time.Second)
            if err != nil {
               log.Printf( "p.Send error : %v , e = %v , i = %d\n", err , e, i)
            }
         }
      }()
   }

   for i := 0; i < 5; i++ {
      e := email.NewEmail()
      // 设置发送邮件的基本信息
      e.From = "xx <xxx@qq.com>"
      e.To = []string{"xxx@qq.com"}
      e.Subject = "test email.NewPool " + fmt.Sprintf("  the %d email",i)
      e.Text = []byte(fmt.Sprintf("test email.NewPool , the %d email !", i))
      ch <- e
   }

   // 关闭通道
   close(ch)
   // 等待子协程退出
   wg.Wait()
   log.Println("send successfully ... ")
}

运行上述代码后,咱们查看邮箱

咱们可以看到,邮件并不是按照顺序发送出来的,这就对了

要是对于上述的sync 使用有疑问的话, 欢迎查看文章GO通道和 sync 包的分享

总结

  • 分享了邮件,电子邮件是什么
  • 邮件协议有哪些
  • 如何使用GOLANG 发送电子邮件
  • 发送电子邮件如何携带纯文本,HTML内容,附件等
  • 发送邮件,如何抄送,如何密送
  • 如何提高发送邮件的性能

之前我们也简单分享了GOLANG如何发送邮件,大家可以对比一下,哪一种更加方便golang如何发送邮件(qq邮箱) |Go主题月

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里,下一次 分享一波GO的爬虫

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~