golang通过STMP协议发送邮件功能详细操作

发布于:2025-04-12 ⋅ 阅读:(42) ⋅ 点赞:(0)

一.简介

在 Go 语言中接入 IMAP 和 SMTP 服务来进行邮件的发送和接收操作,可以通过使用一些现有的第三方库来简化操作,常见的库有 go-imap 和 gomail,它们可以帮助我们连接和操作 IMAP 邮箱(读取邮件)以及通过 SMTP 发送邮件

二.实现

1. IMAP 服务(读取邮件)

可以使用 go-imap 库来连接 IMAP 服务并读取邮件。首先需要安装该库:

go get github.com/emersion/go-imap
go get github.com/emersion/go-imap-idle
go get github.com/emersion/go-message

下面是一个简单的例子,演示如何连接到 IMAP 服务器并列出邮件:

package main
import (
        "fmt"
        "log"
        "net/mail"
        "github.com/emersion/go-imap"
        "github.com/emersion/go-imap/client"
)
func main() {
        // 连接到IMAP服务器
        c, err := client.DialTLS("imap.example.com:993", nil)
        if err != nil {
                log.Fatal(err)
        }
        defer c.Logout()
        // 登录到邮箱
        if err := c.Login("user@example.com", "password"); err != nil {
                log.Fatal(err)
        }
        // 选择邮件箱
        mbox, err := c.Select("INBOX", false)
        if err != nil {
                log.Fatal(err)
        }
        // 获取前10封邮件
        seqset := new(imap.SeqSet)
        seqset.AddRange(1, 10)
        messages := make(chan *imap.Message, 10)
        go func() {
                if err := c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages); err != nil {
                        log.Fatal(err)
                }
        }()
        for msg := range messages {
                fmt.Println("Subject:", msg.Envelope.Subject)
                from := msg.Envelope.From[0]
                fmt.Println("From:", from.Address)
                // 这里可以进一步解析邮件内容
        }
}

2. SMTP 服务(发送邮件)

发送邮件可以使用 gomail 库(或 mail 库)。首先,安装 gomail:

go get gopkg.in/gomail.v2

然后,可以使用如下代码来通过 SMTP 发送邮件:

package main
import (
        "fmt"
        "gopkg.in/gomail.v2"
)
func main() {
        // 创建邮件消息
        mailer := gomail.NewMessage()
        // 设置发件人名称为“张三”并且发件人邮箱为 sender@example.com
        mailer.SetHeader("From", "张三 <sender@example.com>")  // 设置发件人名称和邮箱
        // 设置收件人邮箱
        mailer.SetHeader("To", "recipient@example.com")
        // 设置邮件主题
        mailer.SetHeader("Subject", "Test Email from Go")
        // 设置邮件正文
        mailer.SetBody("text/plain", "Hello, this is a test email!")
        // 配置SMTP服务器
        dialer := gomail.NewDialer(
                "smtp.example.com",    // SMTP服务器地址
                587,                   // SMTP服务器端口
                "sender@example.com",  // 登录SMTP的邮箱地址(用户名)
                "password123",         // 邮箱登录密码或授权码
        )
        // 发送邮件
        if err := dialer.DialAndSend(mailer); err != nil {
                fmt.Println("发送邮件失败:", err)
        } else {
                fmt.Println("邮件发送成功!")
        }
}

/*
解释:
    • "张三 <sender@example.com>" 这一行就是设置发件人显示名称为 “张三”,而邮件实际发送的是 sender@example.com 这个邮箱地址。
    • 可以将 "张三 <sender@example.com>" 替换为任何希望显示的名称和邮箱地址。

结果:

    当收件人收到邮件时,发件人会显示为:
        From: 张三 <sender@example.com>
    这种格式在大多数邮件客户端中都会正确显示发件人名称和邮箱地址。
    这样,就能灵活地定义发件人名称了
*/



3.配置和参数说明

• IMAP 连接配置:根据你的 IMAP 服务器进行替换(例如 imap.example.com 和端口 993,适合 SSL/TLS)。

• SMTP 连接配置:同样需要替换 SMTP 服务器地址(例如 smtp.example.com)以及邮箱的 SMTP 端口(通常是 587)。

4.总结

• 使用 go-imap 库来接入 IMAP 服务并操作邮件。

• 使用 gomail 库来通过 SMTP 服务发送邮件。

三.案例

控制器方法

package controller

import (
	. "pkg"
	"pkg/mailer"
	"service"

	"proto/common"
	"github.com/gin-gonic/gin"
	"google.golang.org/protobuf/proto"
)

// 发送邮件Handler
func EmailHandler(m proto.Message, ctx *gin.Context) (proto.Message, error) {
	var err error
	res := CommonRes(ctx)

	// 将请求转换为具体的类型
	req, ok := m.(*common.SendEmailCodeReq)
	if !ok {
		res.ErrCode = uint64(CodeInvalidRequestType)
		res.ErrDesc = CodeMap[CodeInvalidRequestType]
		return res, err
	}
	if req.Email == "" {
		res.ErrCode = uint64(CodeReqDataNotEmpty)
		res.ErrDesc = CodeMap[CodeReqDataNotEmpty]
		return res, err
	}
	if req.Type == "" { // 判断发送邮件类型, 如果为空, 则默认为 SMTP方式发送
		req.Type = string(mailer.ServiceSMTP)
	}
	err = service.HandleEmailSender(req)
	if err != nil {
		return res, err
	}

	return res, nil
}

pkg包方法

commonRes.go

package pkg

import (
	"context"

	commonpb "proto/common"
)

const (
	RequestID   = "RequestID"
	RequestBody = "ReqBody"
	UserId      = "userId"
)

// 自定义错误
func NewError(errCode uint64, err error) error {
	return New(int32(errCode), err.Error())
}

type MyError struct {
	Code   int32
	Reason string 
}

func New(code int32, reason string) error {
	return &MyError{Code: code, Reason: reason}
}

// 公共响应信息: 该结构体用于返回所有接口的公共响应信息,不会返回数据
func CommonRes(ctx context.Context) *commonpb.CommonRes {
	resp := &commonpb.CommonRes{
		ErrCode:   uint64(CodeOK), // 默认为200 成功
		ErrDesc:   CodeMap[CodeOK],  // 这里映射可以自行定义
	}

	return resp
}

mailer/mailer.go

package mailer

// Message 定义通用邮件消息结构
type Message struct {
	Subject     string   // 主题
	Body        string   // 正文(支持文本或HTML)
	To          []string // 收件人列表
	CC          []string // 抄送
	BCC         []string // 密送
	Attachments []string // 附件路径(本地文件)
}

// Sender 邮件发送接口
type Sender interface {
	Send(message Message) error
}

mailer/newSender.go

package mailer

// 使用工厂模式创建实例

type ServiceType string

const (
	ServiceSMTP ServiceType = "smtp"
)

// NewSender 根据类型创建邮件发送实例
func NewSender(serviceType ServiceType, config interface{}) Sender {
	switch serviceType {
	case ServiceSMTP:
		return NewSMTPSender(config.(SMTPConfig))
	default:
		panic("不支持的邮件服务类型")
	}
}

mailer/stmpSender.go

package mailer

// SMTP 服务(通用实现,支持腾讯企业邮等)
import (
	"fmt"

	"gopkg.in/gomail.v2"
)

// SMTP配置
type SMTPConfig struct {
	Host     string // 主机
	Port     int    // 端口
	Username string // 用户名
	Name     string // 发件人名称
	Password string // 密码
	From     string // 发件人邮箱
}

type SMTPSender struct {
	config SMTPConfig
}

// NewSMTPSender 创建一个新的 NewSMTPSender 实例
func NewSMTPSender(cfg SMTPConfig) Sender {
	return &SMTPSender{config: cfg}
}

// 发送邮件
func (s *SMTPSender) Send(msg Message) error {
	mailer := gomail.NewMessage()
	mailer.SetHeader("From", fmt.Sprintf("%s <%s>", s.config.Name, s.config.From)) // 发件人邮箱
	mailer.SetHeader("To", msg.To...)                                              // 收件人邮箱
	mailer.SetHeader("Subject", msg.Subject)                                       // 邮件主题
	mailer.SetBody("text/html", msg.Body)                                          // 邮件内容支持HTML

	if len(msg.Attachments) > 0 { // 判断是否需要添加附件
		for _, path := range msg.Attachments {
			mailer.Attach(path)
		}
	}

	// 发送邮件
	dialer := gomail.NewDialer(s.config.Host, s.config.Port, s.config.Username, s.config.Password)
	if err := dialer.DialAndSend(mailer); err != nil {
		return err
	}

	return nil
}

proto包方法

common.pb.go

// 公共响应信息: 该结构体用于返回所有接口的公共响应信息,不会返回数据,如果需要返回数据,请自行在各自的proto中定义
type CommonRes struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// @gotags: json:"errCode"
	ErrCode uint64 `protobuf:"varint,1,opt,name=errCode,proto3" json:"errCode"` // 状态码: 200 成功, 其他失败
	// @gotags: json:"errDesc"
	ErrDesc string `protobuf:"bytes,2,opt,name=errDesc,proto3" json:"errDesc"` // 状态码说明
}

service方法

mail.go

package service

import (
	"fmt"
	"appConfig"
	. "pkg"
	"pkg/mailer"

	"proto/common"
)

// 发送邮件
func HandleEmailSender(req *common.SendEmailCodeReq) error {
	var err error
	code := GetRandomNum()
	// 发送 SMTP 邮件
	if req.Type == string(mailer.ServiceSMTP) {
		err = sendSmtpEmail(req.Email, code)
	}

	if err != nil {
		return NewError(uint64(CodeSendMailMsgCheckError), fmt.Errorf("%s: %s", CodeMap[CodeSendMailMsgCheckError], err.Error()))
	}

	// 将验证码保存到 Redis,有效期 5 分钟
	err = redisClient.SaveEmailCode(req.Email, code)
	if err != nil {
		return NewError(uint64(CodeSendMailMsgCheckError), fmt.Errorf("%s: 保存验证码到redis错误: %s", CodeMap[CodeSendMailMsgCheckError], err.Error()))
	}
	return nil
}

// 发送 SMTP 邮件
func sendSmtpEmail(email, code string) error {
	// 获取配置文件
	mailConfig := appConfig.AppConfig.MailerSmtp

	smtpCfg := mailer.SMTPConfig{
		Host:     mailConfig.Host,
		Port:     mailConfig.Port,
		Username: mailConfig.UserName,
		Password: mailConfig.Password,
		From:     mailConfig.From,
	}
	smtpSender := mailer.NewSender(mailer.ServiceSMTP, smtpCfg)
	err := smtpSender.Send(mailer.Message{
		Subject: "验证码",
		Body:    fmt.Sprintf("您的验证码是: %s", code),
		To:      []string{email},
	})
	if err != nil {
		return err

	}
	return nil
}

appConfig包方法

appConfig.go

package appConfig

// 配置文件
// 定义了 AppConfig 结构体,包含了所有配置项
// 并提供了 init 函数,用于加载配置文件并解析到 AppConfig 全局配置结构体中
// 使用了viper库来处理配置文件的读取和解析,这种方法使得处理复杂的配置数据变得更加直观和简单,尤其是当配置数据结构较深或者配置信息较多时
// 通过结合使用Viper和Go的强类型系统,我们不仅能够提高代码的可读性,还能在编译时就捕获到潜在的错误
import (
	"fmt"

	"mylog"
	"github.com/spf13/viper"
)

// 全局配置
var AppConfig AppConfigStruct

// 全局配置结构体:从config/config.yml配置文件映射
type AppConfigStruct struct {
	MailerSmtp struct { // 邮件配置 smtp方式
		// mapstructure 用于将 map 结构映射到 Go 结构体中, 首先,确保你已经在 Go 项目中导入了 mapstructure 库
		Host     string `mapstructure:"host"`
		Port     int    `mapstructure:"port"`
		UserName string `mapstructure:"userName"`
		Password string `mapstructure:"password"`
		From     string `mapstructure:"from"`
		Name     string `mapstructure:"name"`
	}
}

// 加载并解析配置文件:  这里可以在main.go中引入
func InitConfig() {
	// 使用viper库来处理配置文件的读取和解析
	v := viper.New()
	v.SetConfigName("config/config") // 配置文件名称(不含扩展名)
	v.AddConfigPath(".")             // 查找配置文件的目录(此处为当前工作目录)
	v.SetConfigType("yaml")          // 配置文件类型(如 yaml、json、toml 等)

	// 读取配置文件
	if err := v.ReadInConfig(); err != nil {
		mylog.Infof(fmt.Sprintf("Error reading config YAML file: %s", err.Error()))
	}
	// 将配置文件的内容自动绑定到AppConfig结构体实例
	if err := v.Unmarshal(&AppConfig); err != nil {
		mylog.Infof(fmt.Sprintf("Unable to decode into struct: %s", err.Error()))
	}
}

config.yml配置

mailerSmtp: # 邮件配置 smtp方式
  host: "smtp.xxx.cn"
  port: 465
  userName: "xxx@xxx.net"
  name: "xxx"
  password: "xxx"
  from: "xxx@xxx.net" 

好了, 接入 IMAP 和 SMTP 服务来进行邮件的发送和接收操作就完成了, 制作不易, 请转发收藏点赞~


网站公告

今日签到

点亮在社区的每一天
去签到