📌Golang📌应用📌smtp发送邮件.txt
"net/smtp"包实现了简单邮件传输协议(SMTP),包已冻结不再接受新功能。

func CRAMMD5Auth(username, secret string) Auth
返回一个实现了CRAM-MD5身份认证机制(参见RFC-2195)的Auth接口。
返回的接口使用给出的用户名和密码,采用响应——回答机制与服务端进行身份认证。

func PlainAuth(identity, username, password, host string) Auth
返回一个实现了PLAIN身份认证机制(参见RFC-4616)的Auth接口。
返回的接口使用给出的用户名和密码,通过TLS连接到主机认证,
采用identity为身份管理和行动(通常应设identity为"",以便使用username为身份)。

func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
SendMail连接到addr指定的服务器;如果支持会开启TLS;如果支持会使用a认证身份;
然后以from为邮件源地址发送邮件msg到目标地址to。(可以是多个目标地址:群发)

func NewClient(conn net.Conn, host string) (*Client, error)
使用已经存在的连接conn和作为服务器名的host(用于身份认证)来创建一个*Client

func Dial(addr string) (*Client, error)
返回一个连接到地址为addr的SMTP服务器的*Client;addr必须包含端口号。

func (c *Client) Extension(ext string) (bool, string)
返回服务端是否支持某个扩展,扩展名是大小写不敏感的。
如果扩展被支持,方法还会返回一个包含指定给该扩展的各个参数的字符串。
	例如 c.Extension("AUTH")
	smtp.qq.com 支持的授权方式有: LOGIN PLAIN XOAUTH XOAUTH2
	smtpdm.aliyun.com 支持的授权方式有: PLAIN LOGIN XALIOAUTH

func (c *Client) Auth(a Auth) error
使用提供的认证机制进行认证。失败的认证会关闭该连接。
只有服务端支持AUTH时,本方法才有效。(但是不支持时,调用会默默的成功)

func (c *Client) StartTLS(config *tls.Config) error
发送STARTTLS命令,并将之后的所有数据往来加密。
只有服务器附加了STARTTLS扩展,这个方法才有效。

func (c *Client) Mail(from string) error
发送MAIL命令和邮箱地址from到服务器。
如果服务端支持8BITMIME扩展,本方法会添加BODY=8BITMIME参数。
方法初始化一次邮件传输,后应跟1到多个Rcpt方法的调用。

func (c *Client) Rcpt(to string) error
发送RCPT命令和邮箱地址to到服务器。
调用Rcpt方法之前必须调用了Mail方法,之后可以再一次调用Rcpt方法,也可以调用Data方法。

func (c *Client) Data() (io.WriteCloser, error)
发送DATA指令到服务器并返回一个io.WriteCloser,用于写入邮件信息。
调用者必须在调用c的下一个方法之前关闭这个io.WriteCloser。
方法必须在一次或多次Rcpt方法之后调用。

func (c *Client) Reset() error
向服务端发送REST命令,中断当前的邮件传输。

func (c *Client) Close() error
关闭连接。

func (c *Client) Quit() error
发送QUIT命令并关闭到服务端的连接。

========== ========== ========== ========== ==========

package email

import (
	"bytes"
	"net"
	"net/smtp"
	"slices"
	"strings"
	"time"
)

type Email interface {
	Send(*SendArgs) error
}

type email struct {
	addr string
	user string
	auth smtp.Auth
}

type Config struct {
	Host   string // eg: smtp.qq.com
	Port   string // 通常为25(普通)、465(TLS)
	User   string // eg: test.message@foxmail.com
	Pwd    string // PLAIN 授权码
	Secret string // CRAM-MD5 密钥
}

func New(cfg *Config) Email {
	var auth smtp.Auth
	if cfg.Pwd != "" {
		auth = smtp.PlainAuth("", cfg.User, cfg.Pwd, cfg.Host)
	} else {
		auth = smtp.CRAMMD5Auth(cfg.User, cfg.Secret)
	}
	return &email{
		addr: net.JoinHostPort(cfg.Host, cfg.Port),
		user: cfg.User,
		auth: auth,
	}
}

const (
	br        = "\r\n"
	plainType = "Content-Type: text/plain; charset=UTF-8"
	htmlType  = "Content-Type: text/html; charset=UTF-8"
)

type SendArgs struct {
	Subject  string   // 邮件标题
	FromName string   // 发件人别名(非邮箱格式),为空默认显示成邮箱用户名
	ReplyTo  string   // 回复邮箱,点击回复自动填入收件人的邮箱账号
	To       []string // 收件邮箱
	Cc       []string // 抄送邮箱
	Bcc      []string // 密送邮箱
	IsHtml   bool     // 是否为html模板
	Body     []byte   // 邮件正文
}

func (e *email) Send(args *SendArgs) error {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("Date: ")
	buf.WriteString(time.Now().Format(time.RFC1123Z))
	buf.WriteString(br)
	buf.WriteString("Subject: ")
	buf.WriteString(args.Subject)
	buf.WriteString(br)
	from := e.user
	if args.FromName != "" {
		from = args.FromName + " <" + e.user + ">"
	}
	buf.WriteString("From: ")
	buf.WriteString(from)
	buf.WriteString(br)
	if args.ReplyTo != "" {
		buf.WriteString("Reply-To: ")
		buf.WriteString(args.ReplyTo)
		buf.WriteString(br)
	}
	buf.WriteString("To: ")
	buf.WriteString(strings.Join(args.To, ";"))
	buf.WriteString(br)
	if len(args.Cc) > 0 {
		buf.WriteString("Cc: ")
		buf.WriteString(strings.Join(args.Cc, ";"))
		buf.WriteString(br)
	}
	bodyType := plainType
	if args.IsHtml {
		bodyType = htmlType
	}
	buf.WriteString(bodyType)
	buf.WriteString(br)
	buf.WriteString(br) // body和head之间必须空一行
	buf.Write(args.Body)
	return smtp.SendMail(e.addr, e.auth, e.user, slices.Concat(args.To, args.Cc, args.Bcc), buf.Bytes())
}

========== ========== ========== ========== ==========

func main() {
	e := email.New(&email.Config{
		Host:   "smtp.qq.com",
		Port:   "587",
		User:   "service@foxmail.com",
		Pwd:    "ebnxxxxxxxxxxxehc",
		Secret: "",
	})
	err := e.Send(&email.SendArgs{
		Subject:  "您有新的工单待处理",
		FromName: "系统通知助手",
		ReplyTo:  "no-reply@foxmail.com",
		To:       []string{"xxxxxx@126.com", "xxxxxx@163.com"},
		Cc:       []string{"xxxxxx@qq.com", "xxxxxx@foxmail.com"},
		Bcc:      []string{"xxxxxx@vip.qq.com", "xxxxxx@gmail.com"},
		IsHtml:   false,
		Body:     []byte("这里是邮件正文内容"),
	})
	log.Println(err)
}

========== ========== 使用gomail包 ========== ==========

import (
	"gopkg.in/gomail.v2"
	"io"
	"log"
)

func GomailExample() {
	m := gomail.NewMessage()
	m.SetHeader("Subject", "您有新的工单待处理")
	m.SetHeader("From", "service@foxmail.com") // 必填且必须和账号名相同
	m.SetHeader("Reply-To", "no-reply@foxmail.com")
	m.SetHeader("To", "xxxxxx@126.com", "xxxxxx@163.com")
	m.SetHeader("Cc", "xxxxxx@qq.com", "xxxxxx@foxmail.com")
	m.SetHeader("Bcc", "xxxxxx@vip.qq.com", "xxxxxx@gmail.com")
	m.SetBody("text/plain", "这里是邮件正文内容")
	m.Attach("f1.txt", gomail.SetCopyFunc(func(w io.Writer) error {
		_, err := w.Write([]byte("hello one."))
		return err
	}))
	m.Attach("f2.txt", gomail.SetCopyFunc(func(w io.Writer) error {
		_, err := w.Write([]byte("hello two."))
		return err
	}))
	d := gomail.NewDialer("smtp.qq.com", 587, "service@foxmail.com", "ebnxxxxxxxxxxxehc")
	err := d.DialAndSend(m)
	log.Println(err)
}