Files
mailx/smtp/smtp.go
T

268 lines
6.8 KiB
Go
Raw Normal View History

2024-11-20 19:42:07 +08:00
package smtp
import (
"bytes"
"context"
2025-10-30 14:13:18 +08:00
"crypto/tls"
2024-11-20 19:42:07 +08:00
"encoding/base64"
"errors"
"fmt"
"net/smtp"
"os"
"path"
"strings"
"time"
"code.yun.ink/pkg/mailx/interfaces"
)
// 邮件发送的封装
// 1. 支持文本
// 2. 支持文件
type Smtp struct {
interfaces.DefaultEmail
2025-08-10 21:17:10 +08:00
// params *interfaces.EmailConfigDataSmtp
auth smtp.Auth
// logger loggerx.LoggerInterface
2024-11-20 19:42:07 +08:00
}
2025-08-10 21:17:10 +08:00
func NewSmtp() *Smtp {
smtp := &Smtp{}
smtp.Options = interfaces.DefaultOptions()
2025-08-30 18:14:09 +08:00
smtp.EmailType = interfaces.EmailTypeSmtp
2025-08-10 21:17:10 +08:00
return smtp
}
2024-11-20 19:42:07 +08:00
2025-08-10 21:17:10 +08:00
func (l *Smtp) SetOption(ctx context.Context, opt ...interfaces.Option) (interfaces.EmailInterface, error) {
for _, o := range opt {
o(&l.Options)
}
l.Options.Logger.Infof(ctx, "params:%+v", l.Options.Smtp)
if l.Options.Smtp == nil {
2024-11-20 19:42:07 +08:00
return nil, errors.New("not smtp")
}
2025-08-10 21:17:10 +08:00
return l, nil
2024-11-20 19:42:07 +08:00
}
func (l *Smtp) Send(ctx context.Context, message interfaces.Message) error {
2025-08-10 21:17:10 +08:00
if l.Options.Smtp == nil {
2024-11-20 19:42:07 +08:00
return errors.New("not init")
}
2025-10-30 14:13:18 +08:00
if l.Options.Smtp.IsSSL {
//
return l.SendSSL(ctx, message)
}
return l.SendPlain(ctx, message)
}
// 明文
func (l *Smtp) SendPlain(ctx context.Context, message interfaces.Message) error {
2024-11-20 19:42:07 +08:00
// .Auth()
buffer := bytes.NewBuffer(nil)
boundary := "YunBoundaryYun"
Header := make(map[string]string)
// Header["From"] = "BOP<" + message.Form + ">"
if message.Form != "" {
Header["From"] = message.Form
} else {
2025-08-10 21:17:10 +08:00
Header["From"] = l.Options.Smtp.Username
2024-11-20 19:42:07 +08:00
}
if len(message.To) > 0 {
str := ""
for _, val := range message.To {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["To"] = strings.Trim(str, ",")
// Header["To"] = strings.Join(message.To, ",")
}
if len(message.Cc) > 0 {
str := ""
for _, val := range message.Cc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Cc"] = strings.Trim(str, ",")
// Header["Cc"] = strings.Join(message.Cc, ",")
}
if len(message.Bcc) > 0 {
str := ""
for _, val := range message.Bcc {
name := ""
s := strings.Split(val, "@")
if len(s) > 0 {
name = s[0]
}
str = str + "," + name + "<" + val + ">"
}
Header["Bcc"] = strings.Trim(str, ",")
// Header["Bcc"] = strings.Join(message.Bcc, ",")
}
Header["Subject"] = message.Subject
Header["Content-Type"] = "multipart/mixed; charset=UTF-8; boundary=" + boundary
Header["Date"] = time.Now().String()
Header["Reply-To"] = message.ReplyTo
Header["X-Priority"] = "3"
l.writeHeader(buffer, Header)
body := "--" + boundary + "\r\n"
// body += "Content-Type: text/plain; charset=UTF-8 \r\n"
body += "Content-Type: text/html;charset=utf-8\r\n"
body += "Content-Transfer-Encoding:quoted-printable\r\n\r\n"
// body += "<html><body><h1>huang</h1><h2>xin</h2></body></html>\r\n"
// body += "<html><body>" + message.Body + "</body></html>\r\n"
body += message.Body + "\r\n"
// body += "--" + boundary + "--\r\n\r\n"
buffer.WriteString(body)
for _, value := range message.Attachment {
newBuf := bytes.NewBuffer(nil)
err := l.writeFile(newBuf, value.Content)
if err != nil {
fmt.Println("file err:", err)
continue
}
f_name := path.Base(value.Content)
attachment := "--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
// attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
buffer.WriteString(attachment)
buffer.WriteString(newBuf.String())
}
buffer.WriteString("\r\n--" + boundary + "--\r\n")
b := buffer.Bytes()
2025-10-30 14:13:18 +08:00
l.auth = smtp.PlainAuth("", l.Options.Smtp.Username, l.Options.Smtp.Password, l.Options.Smtp.Host)
2025-08-10 21:17:10 +08:00
err := smtp.SendMail(l.Options.Smtp.Host+":"+l.Options.Smtp.Port, l.auth, l.Options.Smtp.Username, message.To, b)
2024-11-20 19:42:07 +08:00
return err
}
2025-10-30 14:13:18 +08:00
// SSL
func (l *Smtp) SendSSL(ctx context.Context, message interfaces.Message) error {
// 邮件内容格式(必须符合SMTP协议规范)
contentType := "Content-Type: text/html; charset=UTF-8"
msg := []byte("To: " + message.To[0] + "\r\n" +
"From: " + message.Form + "\r\n" +
"Subject: " + message.Subject + "\r\n" +
contentType + "\r\n\r\n" +
message.Body)
// 解析收件人(支持多个)
// toList := strings.Split(message.To, ",")
// 1. 建立TCP连接(SSL通常用465端口)
if l.Options.Smtp.Port == "" {
l.Options.Smtp.Port = "465"
}
host := l.Options.Smtp.Host + ":" + l.Options.Smtp.Port
conn, err := tls.Dial("tcp", host, &tls.Config{
InsecureSkipVerify: false, // 生产环境不建议跳过证书验证
ServerName: strings.Split(host, ":")[0], // 服务器域名(用于证书验证)
})
if err != nil {
return fmt.Errorf("连接服务器失败: %v", err)
}
defer conn.Close()
// 2. 创建SMTP客户端
client, err := smtp.NewClient(conn, strings.Split(host, ":")[0])
if err != nil {
return fmt.Errorf("创建SMTP客户端失败: %v", err)
}
// 3. 身份验证(使用登录邮箱和授权码)
auth := smtp.PlainAuth("", l.Options.Smtp.Username, l.Options.Smtp.Password, strings.Split(host, ":")[0])
if err := client.Auth(auth); err != nil {
return fmt.Errorf("身份验证失败: %v", err)
}
// 4. 设置发件人
if err := client.Mail(l.Options.Smtp.Username); err != nil {
return fmt.Errorf("设置发件人失败: %v", err)
}
// 5. 设置收件人(支持多个)
for _, addr := range message.To {
if err := client.Rcpt(addr); err != nil {
return fmt.Errorf("设置收件人 %s 失败: %v", addr, err)
}
}
// 6. 发送邮件内容
w, err := client.Data()
if err != nil {
return fmt.Errorf("获取数据写入流失败: %v", err)
}
_, err = w.Write(msg)
if err != nil {
return fmt.Errorf("写入邮件内容失败: %v", err)
}
err = w.Close()
if err != nil {
return fmt.Errorf("关闭写入流失败: %v", err)
}
// 7. 退出SMTP会话
return client.Quit()
}
2024-11-20 19:42:07 +08:00
// 格式化header
func (l *Smtp) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
header := ""
// header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
for key, value := range Header {
if value != "" {
header += key + ": " + value + "\r\n"
}
}
header += "\r\n"
buffer.WriteString(header)
return header
}
// 格式化文件
func (l *Smtp) writeFile(buffer *bytes.Buffer, fileName string) error {
file, err := os.ReadFile(fileName)
if err != nil {
return err
}
payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
base64.StdEncoding.Encode(payload, file)
buffer.WriteString("\r\n")
for index, line := 0, len(payload); index < line; index++ {
buffer.WriteByte(payload[index])
if (index+1)%76 == 0 {
buffer.WriteString("\r\n")
}
}
return nil
}