go发送邮件

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式,绝大多数语言如C++ Java等都通过库的方式支持了他,当然,go语言也不会例外

很重要

这个一定要写在前面,建议自己尝试的时候呢,多申请几个邮箱.
不要用自己的常用邮箱.
不要用自己的常用邮箱.
不要用自己的常用邮箱.
国内大多数邮箱都有数量限制,单日发的太多容易被封.

smtp库

smtp标准库就是golang实现邮件发送的一个标准库,你可以查看src中的net/stmp来查看他的源码

stmp库只有两个文件,auth.gosmtp.go,前者是用来验证的,后者是用发发送邮件的,auth.go比较简单,通常也不会自己去实现,所以仅做了解即可,用发是固定的,后面的例子会演示

我们主要来看一下smtp.go中的SendMail()方法,下面为函数签名

func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {}

参数含义如下
addr  邮件服务器地址,一般与端口一起,如腾讯的邮箱 smtp.exmail.qq.com:25
a       通过Auth.go中的PlainAuth()方法验证发件人用户名密码后获得
from  发件人邮箱
to      收件人邮箱列表
msg   邮件内容

利用smtp来发送邮件

smtp库不需要我们视线接口,我们只需要构造好参数,通过SendMail()来发送邮件即可

我们来模拟向腾讯邮箱中发送一封邮件,首先来构造参数,腾讯邮件服务器地址为smtp.qq.com,端口为25(大部分邮件服务器的端口都是25),常用邮箱的邮件服务器地址我会在最后在备注中贴出

addr := "smtp.qq.com:25"

除此之外,我们还需要构造a,from,to,msg四个参数

首先 使用auth.go中的PlainAuth()来生成a,也就是Auth类型,这边是需要发件人的邮箱,邮件服务器,邮箱密码,我这里演示通过我的一个qq邮箱向我的另外一个qq邮箱发送邮件

获取auth时如果不是TLS连接第一个参数可以给空字符串,我们一般邮件都不需要指定第一个参数,后面参数就分别为 邮箱 密码 邮箱所在的邮件服务器

auth := smtp.PlainAuth("", "你的qq号码@qq.com", "password", "smtp.qq.com")

from发件人也就是上面验证auth用到的邮箱,zhangsheng_1992@sohu.com

from := "你的qq号码@qq.com"

to为收件人,是一个[]string

to := []string{"收件人qq号码@qq.com"}

最后是msg

msg := []byte("Hello World!")

整体代码如下:

package main

import (
    "fmt"
    "net/smtp"
)

func main() {
    addr := "smtp.qq.com:25"
    auth := smtp.PlainAuth("", "115354xxxxx@qq.com", "password", "smtp.qq.com")
    from := "115354xxxxx@qq.com"
    to := []string{"2974xxxxx@qq.com"}
    msg := []byte("Hello World!")
    err := smtp.SendMail(addr, auth, from, to, msg)
    fmt.Println(err)
}

注意: 大部分个人邮箱都有smtp保护,所以你可能需要登陆邮箱设置一下

比如qq邮箱你需要设置一个授权码,保护你的密码嘛
点击设置,账户,开启pop3/smtp邮件保护,开启服务的第一个选项,根据提示获取授权码,然后在上面程序密码处填写授权码


qqmail.png

而sohu邮箱就简单些,勾选开启服务即可


sohumail.png

其他邮箱的设置大同小异,请自行设置,运行程序,然后去邮箱查看


receivemail.png

构造邮件头

我门发现上面收到的邮件没有标题,也没有发件人,更没有收件人及内容信息,这是为什么呢?

其实这部分信息在msg中,需要我们来构造,包括
subject 邮件标题
send 邮件接收人,与上面的from区分开,所以我改个名字
receiver 邮件接收人
type 邮件类型 如html邮件 纯文本邮件等等

我门来构造这一部分,注意,这些参数全部时字符串,我们使用fmt.Sprintf()方法来生成这个string,\r\n表示换行,固定格式

    subject := fmt.Sprintf("Subject: %s\r\n", "测试邮件")
    send := fmt.Sprintf("From: %s 测试发件邮箱\r\n", "11535xxx.com")
    receiver := fmt.Sprintf("To: %s\r\n", "2974xxx@qq.com")
    content_type := "Content-Type: text/plain" + "; charset=UTF-8\r\n\r\n"
    content := "Hello World!"
    msg := []byte(subject + send + receiver + content_type + content)
    addr := "smtp.qq.com:25"
    auth := smtp.PlainAuth("", "11535xxxxx@qq.com", "password", "smtp.qq.com")
    from := "11535xxxxx@qq.com"
    to := []string{"2974xxxxx@qq.com"}
    err := smtp.SendMail(addr, auth, from, to, msg)
    fmt.Println(err)

这次我们来看接收到的邮件

receivemail2.png

是不是信息都全了?

关于邮件头,需要记住
1.它们都是字符串拼接而成的,顺序为
邮件标题 \r\n
发件人 \r\n
收件人 \r\n
邮件类型与字符编码 \r\n\r\n
邮件内容

2.邮件类型与邮件内容之间需要2个换行,即2组\r\n,否则无法看到邮件内容,就如同第一次那样

3.如果接收到的邮件为乱码,那么检查字符编码

发送html邮件

发送html邮件,仅需要设置content_type,也就是邮件类型中的Content-Type: text/html;即:

content_type = "Content-Type: text/html; charset=UTF-8\r\n\r\n" //和邮件内容间两组换行

一般我门不会把html当做一个字符串写到程序中,那样修改起来很麻烦,我们通常的做法是将html写成一个模版,然后读区这个模版,新建一个temp.html,内容如下:

<html>
    <head>
        <meta charset="utf-8" />
        <title>测试邮件</title>
    </head>

    <body>
            <p class="title">我是测试邮件</p>
            <p>内容1: %s</p>
            <p>内容2: %s</p>
            <img src="%s" alt="图片"/>
            <p><a href="%s">查看详情</a>
    </body>
</html>

然后通过os.Open()打开并读取这个模版

file, _ := os.Open("temp.html")
    defer file.Close()
    p := make([]byte, 2<<10)//设置大一点 让一次性读进去
    if _, err := file.Read(p); err != nil {
        fmt.Println(err)
        os.Exit(0)
    }
    content := fmt.Sprintf(string(p), "1", "2", "http://img3.doubanio.com/lpic/s24524807.jpg", "4")//去网上随便找的张图 测试而已,如果你收到的邮件图片打不开请换一个图片链接(随便网上找张图,右键获取图片地址)

其他部分不变,记得设置content_type 编码,再次发送

golang的吉祥物就被我们通过邮件发出去了,当然你还可以增加一些css和js来丰富一封html邮件的,但这不是本文重点

完整代码

package main

import (
    "fmt"
    "net/smtp"
    "os"
)

func main() {
    file, _ := os.Open("temp.html")
    defer file.Close()
    p := make([]byte, 2<<10)
    if _, err := file.Read(p); err != nil {
        fmt.Println(err)
        os.Exit(0)
    }
    content := fmt.Sprintf(string(p), "1", "2", "http://img3.doubanio.com/lpic/s24524807.jpg", "4")
    subject := fmt.Sprintf("Subject: %s\r\n", "测试邮件")
    send := fmt.Sprintf("From: %s 测试发件邮箱\r\n", "11535xxxxx.com")
    receiver := fmt.Sprintf("To: %s\r\n", "2974xxxxx@qq.com")
    content_type := "Content-Type: text/html" + "; charset=UTF-8\r\n\r\n"
    msg := []byte(subject + send + receiver + content_type + content)
    addr := "smtp.qq.com:25"
    auth := smtp.PlainAuth("", "11535xxxxx@qq.com", "paaword", "smtp.qq.com")
    from := "11535xxxxx@qq.com"
    to := []string{"2974xxxxx@qq.com"}
    err := smtp.SendMail(addr, auth, from, to, msg)
    fmt.Println(err)
}

发送附件

一个标准的邮件包含 邮件头,邮件内容,附件,下面,我就来介绍如何发送一个带附件的邮件
与发送html相同,发送一个带附件的邮件,也需要指定Content-Type参数,并指定一个分隔符,将邮件头,正文部分附件分隔开,分隔符可以任意设置

content_type := "Content-Type: multipart/mixed; boundary=分隔符\r\n"

分隔符以 --分隔符 开头,以--分隔符--结尾,然后,为了兼容性,需要将附件编码,为了增加理解,我在代码中增加了过多注释,可能影响阅读体验,建议拷贝下来自己边运行边理解,一定要注意换行!!!!

package main
import (
    "encoding/base64"
    "fmt"
    //  "io/ioutil"
    "net/smtp"
    "os"
)
func main() {
    //mailcontent用来保存邮件的有所内容
    var mailcontent string
    //设置内容间的分隔符 尽量不要用汉字,除此之外设置成什么都可以
    delimiter := "module"
    //设置头信息头部分
    subject := fmt.Sprintf("Subject: %s\r\n", "测试邮件")
    send := fmt.Sprintf("From: %s 测试发件邮箱\r\n", "11535xxxxx@qq.com")
    receiver := fmt.Sprintf("To: %s\r\n", "2974xxxxx@qq.com") + "MIME-Version: 1.0\r\n"
    content_type := "Content-Type: multipart/mixed; boundary=" + delimiter + "\r\n"
    //邮件描述 这个可以不设置也没关系
    content_type += "Content-Description: 我有附件\r\n"
    header := subject + send + receiver + content_type
    //邮件头部分设置完毕

    //分割头信息与普通文本邮件 重要!!!!!!!
    mailcontent = header + fmt.Sprintf("\r\n--%s\r\n", delimiter)
    //你可以同时发送html 但需要注意分隔 你可以把这段注释掉只发送html邮件
    htmlcontent := "Content-Type: text/html; charset=UTF-8\r\n\r\n"
    htmlcontent += `<html><head></head><body><h1 style="color:red">我是html内容</h1></body></html>`
    mailcontent += htmlcontent

    //附件部分
    filepath := "/Users/zhangsheng/go/a.png"
    filename := "a.png"
    //分隔 html内容与附件内容
    fileheader := fmt.Sprintf("\r\n--%s\r\n", delimiter)
    fileheader += "Content-Type: application/octet-stream\r\n"
    fileheader += "Content-Description: 附一个Go文件\r\n"
    fileheader += "Content-Transfer-Encoding: base64\r\n"
    fileheader += "Content-Disposition: attachment; filename=\"" + filename + "\"\r\n\r\n"

    //读取附件内容并编码
    file, _ := os.Open(filepath)
    fileinfo := make([]byte, 2<<16)//这边需要设置一个足够大的容量来保证可以一次性读取,后面学习ioutil和bufio库的时候会学习到更简便的办法
    if n, err := file.Read(fileinfo); err != nil {
        fmt.Println(err)
    } else {
        fileinfo = fileinfo[:n]
    }
    filecontent := base64.StdEncoding.EncodeToString(fileinfo)
    // 我们可以使用ioutil库来实现读取 这样就不用像os Read()设置一个足够大的[]byte
    //  fileinfo, _ := ioutil.ReadFile(filepath)
    //  filecontent := make([]byte, base64.StdEncoding.EncodedLen(len(attaData)))
    //  base64.StdEncoding.Encode(filecontent, attaData)
    filemessage := fileheader + filecontent
    //分隔结束
    mailcontent += filemessage + "\r\n--" + delimiter + "--\r\n\r\n"

    msg := []byte(mailcontent)
    fmt.Println(string(msg))
    addr := "smtp.qq.com:25"
    auth := smtp.PlainAuth("", "11535xxxxx@qq.com", "password", "smtp.qq.com")
    from := "11535xxxxx@qq.com"
    to := []string{"297xxxxx@qq.com"}
    err := smtp.SendMail(addr, auth, from, to, msg)
    fmt.Println(err)
}

输出的内容如下:
Subject: 测试邮件
From: 11535xxxxx@qq.com 测试发件邮箱
To: 2974xxxxx@qq.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=module  看这里!我设置的分隔符叫module
Content-Description: 这是一封带附档的邮件

--module 第一个分隔符将邮件人信息与邮件内容分隔开
Content-Type: text/html; charset=UTF-8

<html><head></head><body><h1 style="color:red">我是html内容</h1></body></html>
--module 第二个分隔符 将邮件内容与附件分隔开
Content-Type: application/octet-stream
Content-Description: 附一个Png文件
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="a.png"
//这里是很长的一串编码后的结果
--module-- 分隔符结束

如果你要发送多个附件,简单,继续用分隔符分开第一个附件与第二个区分开即可

然后取接收邮件吧


attr.png
htmlattrmail.png

邮件群发

群发邮件也非常简单,无论是简单文件还是html文件
首先在receiver里用;隔开收件人,如:

receiver := fmt.Sprintf("To: %s\r\n", "2974xxxxx@qq.com;b.qq.com;c.qq.com")

然后在to切片中也增加收件人就可以啦

to := []string{"2974xxxxx@qq.com","b.qq.com","c.qq.com"}

总结

本章我门讲解了smtp库的m,他实现了邮件发送的规则并且可以用来发送邮件,我们讲解了普通邮件,html邮件和附件,以及组合类型邮件,由于邮件和html并不是讲解重点,所以很多东西并没有讲的很细,比如Content-Type,html只是 分隔符 还有包括内嵌邮件等其他使用上的东西

但掌握了smtp库的用法以后,你仅需要取了解这部分内容,并按要求构造你的邮件字符串就可以

附件--常用邮箱地址

注:pop3为收件箱地址,一些客户端邮件管理程序会使用这个
IMAP是另外一种邮件协议 我门常用的都是SMTP协议

阿里云邮箱(mail.aliyun.com):
POP3服务器地址:pop3.aliyun.com(SSL加密端口:995;非加密端口:110)
SMTP服务器地址:smtp.aliyun.com(SSL加密端口:465;非加密端口:25)
IMAP服务器地址:imap.aliyun.com(SSL加密端口:993;非加密端口:143)

谷歌邮箱(google.com):
POP3服务器地址:pop.gmail.com(SSL启用端口:995)
SMTP服务器地址:smtp.gmail.com(SSL启用端口:587)

新浪邮箱(sina.com):
POP3服务器地址:pop3.sina.com.cn(端口:110)
SMTP服务器地址:smtp.sina.com.cn(端口:25)

Tom邮箱(top.com):
POP3服务器地址:pop.tom.com(端口:110)
SMTP服务器地址:smtp.tom.com(端口:25)

网易邮箱(163.com):
POP3服务器地址:pop.163.com(端口:110)
SMTP服务器地址:smtp.163.com(端口:25)

126邮箱:
POP3服务器地址:pop.live.com(端口:995)
SMTP服务器地址:smtp.126.com(端口:25)

雅虎邮箱(yahoo.com):
POP3服务器地址:pop.mail.yahoo.com
SMTP服务器地址:smtp.mail.yahoo.com

雅虎中国(yahoo.com.cn):
POP3服务器地址:pop.mail.yahoo.com.cn(端口:995)
SMTP服务器地址:smtp.mail.yahoo.com.cn(端口:587)
雅虎邮箱POP3的SSL不启用端口为110,POP3的SSL启用端口995;SMTP的SSL不启用端口为25,SMTP的SSL启用端口为465。

Foxmail邮箱(foxmail.com):
POP3服务器地址:POP.foxmail.com(端口:110)
SMTP服务器地址:SMTP.foxmail.com(端口:25)

QQ邮箱(mail.qq.com)
POP3服务器地址:pop.qq.com(端口:110)
SMTP服务器地址:smtp.qq.com(端口:25)
SMTP服务器需要身份验证。

搜狐邮箱(sohu.com):
POP3服务器地址:pop3.sohu.com(端口:110)
SMTP服务器地址:smtp.sohu.com(端口:25)

HotMail邮箱(hotmail.com):
POP3服务器地址:pop.live.com(端口:995)
SMTP服务器地址:smtp.live.com(端口:587

移动139邮箱:
POP3服务器地址:POP.139.com(端口:110)
SMTP服务器地址:SMTP.139.com(端口:25)

中华网邮箱(china.com):
POP3服务器地址:pop.china.com(端口:110)
SMTP服务器地址:smtp.china.com(端口:25)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容