Help us shape the future of Go! Take the 2026 Go Developer Survey

mail

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package mail 提供 SMTP 发信能力。

对外只暴露 Sender 接口与一个后台队列:业务模块把邮件丢进队列就返回, 绝不在请求路径上等 SMTP——对方服务器慢一秒,用户的评论就要多等一秒。

Index

Constants

View Source
const (
	// EncryptionNone 明文连接,仅适合同机或内网的中继。
	EncryptionNone = "none"
	// EncryptionStartTLS 先明文连接再升级为 TLS,对应 587 端口。
	EncryptionStartTLS = "starttls"
	// EncryptionTLS 直接建立 TLS 连接,对应 465 端口。
	EncryptionTLS = "tls"
)

传输加密方式。

View Source
const EnvSMTPPassword = "LUMO_SMTP_PASSWORD"

EnvSMTPPassword 是 SMTP 口令的兜底环境变量。

口令的正规去处是后台「邮件发送」里的口令字段(加密入库、接口不回传), 环境变量留给不便改后台的部署:容器编排里注入密钥、或者口令由运维统管。 两处都有时以后台为准——站长刚在界面上填的那一个才是他此刻的意图。

View Source
const GroupMail = "mail"

GroupMail 是发信设置分组。

View Source
const Name = "mail"

Name 是模块名,也是 App.Provide 的键。

Variables

View Source
var ErrDisabled = errors.New("未启用邮件发送")

ErrDisabled 表示未启用发信。

View Source
var ErrMissingPassword = fmt.Errorf(
	"已配置 SMTP 用户名但没有口令:请在「设置 → 邮件发送」里填写,或设置环境变量 %s", EnvSMTPPassword)

ErrMissingPassword 表示配置了用户名却没有任何口令可用。

Functions

This section is empty.

Types

type Message

type Message struct {
	// To 是收件人地址,至少一个。
	To []string
	// Subject 是主题。
	Subject string
	// Text 是纯文本正文,必填:不是所有客户端都渲染 HTML。
	Text string
	// HTML 是可选的 HTML 正文,作为替代部分随信发出。
	HTML string
}

Message 是一封待发送的邮件。

func (*Message) Validate

func (m *Message) Validate() error

Validate 校验邮件的基本形态。

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module 是邮件模块。本模块无数据库表,故不实现 Migrator。

func New

func New() *Module

New 构造模块。

func (*Module) Close

func (m *Module) Close(ctx context.Context) error

Close 实现 app.Closer:有序停止邮件队列。

app.Close 在 HTTP 排空之后执行(cmd/lumo/serve.go 的 defer),到这里不会 再有业务请求入队。Service.Shutdown 在 ctx 期限内排空剩余邮件,随后取消 消费协程;仍未投递的数量写入日志,便于评估停机窗口内丢失的邮件。

func (*Module) Name

func (m *Module) Name() string

Name 实现 app.Module。

func (*Module) Register

func (m *Module) Register(a *app.App) error

Register 实现 app.Module。

func (*Module) Routes

func (m *Module) Routes(r app.Router)

Routes 实现 app.RouteProvider:只挂一个「发送测试邮件」端点。

SMTP 配错是最常见的运维问题,而错在哪只有真发一封才知道。

func (*Module) Service

func (m *Module) Service() *Service

Service 返回发信服务,供其他模块在未经 App.Lookup 时直接取用。

func (*Module) Settings

func (m *Module) Settings() []app.SettingGroup

Settings 实现 app.SettingsProvider。

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start 实现 app.Starter:启动后台发信协程。

消费协程刻意不直接绑定信号 ctx:SIGTERM 到达后 HTTP 才开始排空,在途请求 仍会入队,此时消费者必须继续工作,直到 Close 在 HTTP 排空后有序停止它。 这里用 WithoutCancel 断开取消传播,改由 Close 负责取消。

type Sender

type Sender interface {
	Send(ctx context.Context, msg *Message) error
}

Sender 发送一封邮件并等待结果。

业务代码通常不直接用它,而是用 Service.Enqueue 走后台队列。

func NewSMTPSender

func NewSMTPSender(cfg *Settings, password string) (Sender, error)

NewSMTPSender 按配置构造发信器。

每次发信重新拨号:CMS 的发信频率很低,长连接带来的复杂度(超时、断线重连、 服务器主动断开)远大于收益。

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service 是发信队列:Enqueue 立即返回,后台单协程按序发送并重试。

func From

func From(a *app.App) *Service

From 取回发信服务;mail 模块未装配时返回 nil。

调用方须能在 nil 时退化(不发通知即可),以便单独测试其他模块。

func NewService

func NewService(svc *settings.Service, logger *slog.Logger) *Service

NewService 构造发信服务;settings 可为 nil(此时始终视为未启用)。

func (*Service) Done

func (s *Service) Done() <-chan struct{}

Done 在消费协程退出后关闭,供停机流程等待其收尾。

func (*Service) Enabled

func (s *Service) Enabled(ctx context.Context) bool

Enabled 报告当前是否启用了发信。

func (*Service) Enqueue

func (s *Service) Enqueue(ctx context.Context, msg *Message)

Enqueue 把邮件放入队列,立即返回。

未启用发信时静默丢弃:站点没配 SMTP 是常态,不该让每次评论都在日志里刷错误。 停机开始后同样拒绝入队,但会记一条调试日志,便于排查停机窗口内的丢信。

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run 运行后台发送协程,直到 Shutdown 停止服务。

ctx 只用于单封邮件的发送超时,不作为退出信号:进程收到 SIGTERM 后 HTTP 还要排空,在途请求仍可能入队,消费者若随之退出,这些邮件就无人消费。 退出统一走 Shutdown(由 app.Closer 在 HTTP 排空之后调用), 由它保证「先排空、后停止」的顺序。

func (*Service) Send

func (s *Service) Send(ctx context.Context, msg *Message) error

Send 同步发送一封邮件,供「发送测试邮件」一类需要即时结果的场景使用。

func (*Service) Settings

func (s *Service) Settings(ctx context.Context) Settings

Settings 返回 mail 分组的有效值;未装配设置模块时返回未启用。

func (*Service) Shutdown

func (s *Service) Shutdown(ctx context.Context)

Shutdown 有序停止发信队列:拒绝新入队 → 在 ctx 期限内排空 → 停止消费协程。

必须在 HTTP 服务器排空之后调用(app.Closer 的时机),此时不会再有业务请求 入队。ctx 由 app 的 ShutdownTimeout 约束,等待有界,不会拖住进程退出。 未投递数量在停止时快照,由 Undelivered 读取;调用方负责记录。

func (*Service) Undelivered

func (s *Service) Undelivered() int

Undelivered 返回停机时未能投递的邮件数;Shutdown 完成后调用才有意义。

type Settings

type Settings struct {
	Enabled     bool   `json:"enabled"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	Username    string `json:"username"`
	Password    string `json:"password"`
	Encryption  string `json:"encryption"`
	FromAddress string `json:"fromAddress"`
	FromName    string `json:"fromName"`
}

Settings 是 mail 分组的有效值。

Password 是解密后的明文,只在这条链路里流转:设置服务 → 发信器。 出接口(GET /api/v1/console/settings/mail)时它恒为空串,见 settings.Group.Mask。

func (*Settings) Credential

func (st *Settings) Credential() string

Credential 返回本次发信该用的口令:后台填过就用后台的,否则回退环境变量。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL