mail

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 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