email

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StatusSpam         = "spam"
	StatusClicked      = "clicked"
	StatusOpened       = "opened"
	StatusUnsubscribed = "unsubscribed"
	StatusUnknown      = "unknown"
)

Additional delivery status constants for SendCloud webhook events.

View Source
const (
	StatusSent       = "sent"
	StatusFailed     = "failed"
	StatusPending    = "pending"
	StatusDelivered  = "delivered"
	StatusSoftBounce = "soft_bounce"
	StatusInvalid    = "invalid"
)

Delivery status constants. These mirror the values used by the parent notification package's LogEntry.Status field.

Variables

This section is empty.

Functions

func InitialMailStatus

func InitialMailStatus(kind string) string

InitialMailStatus returns the DB status right after a successful provider send. SMTP starts as "delivered" (synchronous handoff); SendCloud starts as "sent" (async, refined by webhooks).

func ParseSender

func ParseSender(fromField, nameFallback string) (name, email string, err error)

ParseSender parses a "From" header value of the form "Name <email@x.com>" or a bare "email@x.com". When no name is present, nameFallback is returned as the display name. The returned email is validated; an error is returned for malformed input.

func ReplacePlaceholders

func ReplacePlaceholders(template string, vars map[string]any) string

ReplacePlaceholders substitutes every "{{.Key}}" occurrence in template with the stringified value of vars[Key]. Missing keys are left untouched (the placeholder text is preserved).

func ResolveMailLogStatusTransition

func ResolveMailLogStatusTransition(current, incoming string) (next string, apply bool)

ResolveMailLogStatusTransition decides whether to apply an incoming status to a mail log row. It returns the next status and whether the update should be applied.

Rules:

  • Empty incoming status: no change.
  • Same status: no change.
  • Terminal failure incoming: always apply (overrides any success).
  • Terminal failure current: do not downgrade.
  • Higher rank incoming: apply.
  • Lower or equal rank incoming: no change.

func SendCloudEventToStatus

func SendCloudEventToStatus(event string) string

SendCloudEventToStatus maps SendCloud webhook event codes to mail status.

func SendCloudStatusToMailStatus

func SendCloudStatusToMailStatus(status string) string

SendCloudStatusToMailStatus maps a SendCloud delivery status string (e.g. "送达", "无效邮件-地址不存在", "软退信-服务不可达") to a normalized mail log status.

func VerifySendCloudInboundSignature

func VerifySendCloudInboundSignature(event *SendCloudInboundEvent, apiKey string) bool

VerifySendCloudInboundSignature verifies the signature of an inbound webhook event. SendCloud computes the signature as MD5(token + api_key) and sends it in the `signature` field. Pass your API key to verify.

Types

type Channel

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

Channel adapts a Mailer to the notification.Channel interface so it can be registered with a Dispatcher.

func NewChannel

func NewChannel(name string, mailer *Mailer) *Channel

NewChannel creates an email Channel backed by the given Mailer. The channel is enabled by default.

func (*Channel) Enabled

func (c *Channel) Enabled() bool

Enabled reports whether the channel is active.

func (*Channel) Name

func (c *Channel) Name() string

Name returns the channel name.

func (*Channel) Send

func (c *Channel) Send(ctx context.Context, msg notification.Message) error

Send delivers an email Message via the underlying Mailer. HTML bodies are preferred when present.

func (*Channel) SetEnabled

func (c *Channel) SetEnabled(enabled bool)

SetEnabled toggles the channel on or off.

func (*Channel) Type

func (c *Channel) Type() notification.MessageType

Type returns notification.TypeEmail.

type IMAPConfig

type IMAPConfig struct {
	Host     string // IMAP server host (e.g. "imap.qq.com")
	Port     int    // IMAP server port (typically 993 for TLS)
	Username string // account username (full email address)
	Password string // account password or authorization code
	Mailbox  string // mailbox name (default "INBOX")
}

IMAPConfig holds the connection settings for an IMAP server.

type IMAPReader

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

IMAPReader implements MailReader using the IMAP protocol. It connects via TLS, logs in, and fetches messages from the configured mailbox.

func NewIMAPReader

func NewIMAPReader(cfg IMAPConfig) (*IMAPReader, error)

NewIMAPReader creates a new IMAPReader and establishes a connection to the IMAP server. The connection is ready for ReadMessages calls after construction. Call Close to release the connection.

func (*IMAPReader) Close

func (r *IMAPReader) Close() error

Close logs out and closes the IMAP connection.

func (*IMAPReader) DeleteMessage

func (r *IMAPReader) DeleteMessage(id string) error

DeleteMessage marks a message as deleted and expunges it. The message ID should be the UID.

func (*IMAPReader) MarkRead

func (r *IMAPReader) MarkRead(id string) error

MarkRead marks a message as read by adding the \Seen flag. The message ID should be the UID from the fetched message.

func (*IMAPReader) ReadMessages

func (r *IMAPReader) ReadMessages(limit int) ([]*MailMessage, error)

ReadMessages fetches up to limit unread (unseen) messages from the mailbox. If limit <= 0, all unread messages are fetched. Messages are returned newest-first.

func (*IMAPReader) ReadRecentMessages

func (r *IMAPReader) ReadRecentMessages(limit int) ([]*MailMessage, error)

ReadRecentMessages fetches up to limit most recent messages regardless of read state. If limit <= 0, all messages are fetched. Messages are returned newest-first.

type MailAttachment

type MailAttachment struct {
	Filename    string
	ContentType string
	Size        int64
	Data        []byte
}

MailAttachment is a parsed email attachment.

type MailLog

type MailLog struct {
	ID          string    // unique identifier (provider message ID or generated)
	Provider    string    // provider kind: "smtp", "sendcloud", etc.
	ChannelName string    // channel label for multi-channel setups
	ToEmail     string    // recipient address
	Subject     string    // email subject
	HtmlBody    string    // email HTML body
	Status      string    // delivery status (see status constants)
	ErrorMsg    string    // error message on failure
	MessageID   string    // provider-assigned message ID
	RetryCount  int       // number of retry attempts
	SentAt      time.Time // when the send was initiated
	CreatedAt   time.Time // when the log was created
	UpdatedAt   time.Time // when the log was last updated
}

MailLog is a persisted record of an outbound email send attempt. It tracks the delivery lifecycle from initial send through webhook status updates (delivered, opened, bounced, etc.).

type MailLogStore

type MailLogStore interface {
	// CreateMailLog records a successful or accepted send.
	CreateMailLog(log *MailLog) error
	// CreateFailedMailLog records a send that failed after all retries.
	CreateFailedMailLog(log *MailLog) error
	// UpdateMailLogStatusByMessageID updates the status of a log entry
	// identified by its provider message ID. Status transitions follow
	// lifecycle ordering so late webhooks cannot downgrade a more
	// advanced status.
	UpdateMailLogStatusByMessageID(messageID, provider, status, errorMsg string) error
	// GetMailLogByMessageID returns a log entry by provider message ID.
	GetMailLogByMessageID(messageID string) (*MailLog, error)
	// GetMailLogs returns paginated logs, most recent first.
	GetMailLogs(page, pageSize int) ([]*MailLog, int64, error)
	// GetMailLogStats returns status counts.
	GetMailLogStats() (map[string]int64, error)
}

MailLogStore is the persistence abstraction for mail logs. Implementations may use an in-memory map, a database, or any other storage backend.

type MailMessage

type MailMessage struct {
	ID          string      // message ID from headers
	From        string      // sender address
	FromName    string      // sender display name
	To          []string    // recipient addresses
	Cc          []string    // CC addresses
	Subject     string      // subject line
	TextBody    string      // plain text body
	HTMLBody    string      // HTML body
	ReplyTo     string      // Reply-To header
	Date        time.Time   // Date header
	Headers     mail.Header // raw headers
	Attachments []MailAttachment
}

MailMessage is a parsed inbound email message.

func ParseMailMessage

func ParseMailMessage(raw []byte) (*MailMessage, error)

ParseMailMessage parses a raw RFC 822 message (as returned by IMAP FETCH or POP3 RETR) into a MailMessage. It extracts text and HTML bodies, handles multipart/alternative and multipart/mixed, and collects attachments.

type MailProvider

type MailProvider interface {
	// Kind returns a short identifier for the provider (e.g. "smtp").
	Kind() string

	// SendHTMLWith sends an HTML message, applying the given template
	// variables to the subject and body before delivery. It returns the
	// provider-assigned message ID (if any) and an error.
	SendHTMLWith(to, subject, htmlBody string, vars map[string]any) (string, error)

	// SendTextWith sends a plain-text message, applying the given
	// template variables to the subject and body before delivery. It
	// returns the provider-assigned message ID (if any) and an error.
	SendTextWith(to, subject, textBody string, vars map[string]any) (string, error)
}

MailProvider is the interface implemented by email backends (SMTP, third-party HTTP APIs, etc.). Each provider knows how to render and deliver a single message.

type MailReader

type MailReader interface {
	// ReadMessages fetches up to limit unread messages from the mailbox.
	// If limit <= 0, all unread messages are fetched.
	ReadMessages(limit int) ([]*MailMessage, error)
	// MarkRead marks the message with the given ID as read.
	MarkRead(id string) error
	// DeleteMessage deletes the message with the given ID.
	DeleteMessage(id string) error
	// Close closes the reader and releases any resources.
	Close() error
}

MailReader is the abstraction for fetching and reading emails from a mailbox (IMAP, POP3, API-based, etc.).

type Mailer

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

Mailer is a multi-channel email sender with per-provider retry and cross-provider failover. Providers are selected round-robin starting at startingIndex, which advances after every successful send.

func NewMailer

func NewMailer(providers []MailProvider, opts ...MailerOption) *Mailer

NewMailer creates a Mailer over the given providers. At least one provider is required; options may override the retry policy and starting index.

func (*Mailer) Send

func (m *Mailer) Send(ctx context.Context, to, subject, htmlBody string) error

Send delivers an HTML message. It tries providers in round-robin order; each provider is retried up to RetryPolicy.MaxAttempts with exponential backoff before failing over to the next provider.

func (*Mailer) SendText

func (m *Mailer) SendText(ctx context.Context, to, subject, textBody string) error

SendText delivers a plain-text message using the same retry/failover strategy as Send.

func (*Mailer) SendWithTemplate

func (m *Mailer) SendWithTemplate(ctx context.Context, to, subject, templateCode string, vars map[string]any, templateStore notification.TemplateStore) error

SendWithTemplate loads a template from the store, renders it with vars, and sends the result as HTML (falling back to text when the template body is not HTML).

type MailerOption

type MailerOption func(*Mailer)

MailerOption configures a Mailer at construction time.

func WithRetryPolicy

func WithRetryPolicy(p RetryPolicy) MailerOption

WithRetryPolicy overrides the default retry policy.

func WithStartingIndex

func WithStartingIndex(i int) MailerOption

WithStartingIndex sets the initial round-robin provider index.

type MemoryMailLogStore

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

MemoryMailLogStore is a thread-safe in-memory MailLogStore. It is primarily intended for testing and small-scale deployments.

func NewMemoryMailLogStore

func NewMemoryMailLogStore() *MemoryMailLogStore

NewMemoryMailLogStore creates a new empty MemoryMailLogStore.

func (*MemoryMailLogStore) CreateFailedMailLog

func (s *MemoryMailLogStore) CreateFailedMailLog(log *MailLog) error

CreateFailedMailLog records a send that failed after all retries.

func (*MemoryMailLogStore) CreateMailLog

func (s *MemoryMailLogStore) CreateMailLog(log *MailLog) error

CreateMailLog records a successful or accepted send.

func (*MemoryMailLogStore) GetMailLogByMessageID

func (s *MemoryMailLogStore) GetMailLogByMessageID(messageID string) (*MailLog, error)

GetMailLogByMessageID returns a log entry by provider message ID.

func (*MemoryMailLogStore) GetMailLogStats

func (s *MemoryMailLogStore) GetMailLogStats() (map[string]int64, error)

GetMailLogStats returns status counts.

func (*MemoryMailLogStore) GetMailLogs

func (s *MemoryMailLogStore) GetMailLogs(page, pageSize int) ([]*MailLog, int64, error)

GetMailLogs returns paginated logs, most recent first.

func (*MemoryMailLogStore) UpdateMailLogStatusByMessageID

func (s *MemoryMailLogStore) UpdateMailLogStatusByMessageID(messageID, provider, status, errorMsg string) error

UpdateMailLogStatusByMessageID updates the status of a log entry.

type MemoryMailReader

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

MemoryMailReader is a simple in-memory MailReader for testing. It stores messages as raw bytes and parses them on demand.

func NewMemoryMailReader

func NewMemoryMailReader() *MemoryMailReader

NewMemoryMailReader creates a new empty MemoryMailReader.

func (*MemoryMailReader) AddMessage

func (r *MemoryMailReader) AddMessage(id string, raw []byte)

AddMessage adds a raw RFC 822 message to the reader.

func (*MemoryMailReader) Close

func (r *MemoryMailReader) Close() error

Close is a no-op for the in-memory reader.

func (*MemoryMailReader) DeleteMessage

func (r *MemoryMailReader) DeleteMessage(id string) error

DeleteMessage removes a message.

func (*MemoryMailReader) MarkRead

func (r *MemoryMailReader) MarkRead(id string) error

MarkRead marks a message as read.

func (*MemoryMailReader) ReadMessages

func (r *MemoryMailReader) ReadMessages(limit int) ([]*MailMessage, error)

ReadMessages fetches up to limit unread messages.

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts    int           // total attempts per provider (>=1)
	InitialBackoff time.Duration // backoff before the first retry
	MaxBackoff     time.Duration // upper bound for backoff growth
}

RetryPolicy controls retry behaviour for the Mailer.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns a sensible default retry policy: 3 attempts, starting at 1s, capped at 10s.

type SMTPConfig

type SMTPConfig struct {
	Host     string // SMTP server host (e.g. "smtp.example.com")
	Port     int    // SMTP server port (e.g. 25, 465, 587)
	Username string // authentication username (optional)
	Password string // authentication password (optional)
	From     string // sender address, may be "Name <addr@example.com>"
	FromName string // fallback sender display name when From has no name
}

SMTPConfig holds the connection settings for an SMTP server.

type SMTPProvider

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

SMTPProvider sends mail via a plain SMTP server using net/smtp.

func NewSMTPProvider

func NewSMTPProvider(cfg SMTPConfig) *SMTPProvider

NewSMTPProvider constructs an SMTPProvider from the given config.

func (*SMTPProvider) Kind

func (p *SMTPProvider) Kind() string

Kind returns "smtp".

func (*SMTPProvider) SendHTMLWith

func (p *SMTPProvider) SendHTMLWith(to, subject, htmlBody string, vars map[string]any) (string, error)

SendHTMLWith renders the subject and HTML body with vars and sends the message as text/html.

func (*SMTPProvider) SendTextWith

func (p *SMTPProvider) SendTextWith(to, subject, textBody string, vars map[string]any) (string, error)

SendTextWith renders the subject and text body with vars and sends the message as text/plain.

type SendCloudConfig

type SendCloudConfig struct {
	APIUser  string // SendCloud API user
	APIKey   string // SendCloud API key
	From     string // sender address (may be "Name <email@x.com>")
	FromName string // fallback sender display name
	Endpoint string // API endpoint override (defaults to the public endpoint)
}

SendCloudConfig holds SendCloud API credentials and sender defaults.

type SendCloudDeliveryRecord

type SendCloudDeliveryRecord struct {
	EmailID       string `json:"emailId"`
	Status        string `json:"status"`
	SubStatus     string `json:"subStatus"`
	SubStatusDesc string `json:"subStatusDesc"`
	APIUser       string `json:"apiUser"`
	Recipients    string `json:"recipients"`
	RequestTime   string `json:"requestTime"`
	ModifiedTime  string `json:"modifiedTime"`
	SendLog       string `json:"sendLog"`
}

SendCloudDeliveryRecord is a single delivery-status entry returned by the SendCloud emailStatus API.

func (SendCloudDeliveryRecord) ParsedModifiedTime

func (r SendCloudDeliveryRecord) ParsedModifiedTime() time.Time

ParsedModifiedTime returns the parsed modified time, or zero on error.

func (SendCloudDeliveryRecord) ParsedRequestTime

func (r SendCloudDeliveryRecord) ParsedRequestTime() time.Time

ParsedRequestTime returns the parsed request time, or zero on error.

type SendCloudInboundEvent

type SendCloudInboundEvent struct {
	Event         string `json:"event"`           // "route"
	Message       string `json:"message"`         // "mx route"
	Timestamp     int64  `json:"timestamp"`       // unix timestamp
	From          string `json:"from"`            // header From address
	FromName      string `json:"fromname"`        // From display name
	To            string `json:"to"`              // header To address
	ToName        string `json:"toname"`          // To display name
	XMXMailFrom   string `json:"x_mx_mailfrom"`   // envelope sender
	XMXRcptTo     string `json:"x_mx_rcptto"`     // envelope recipient
	Headers       string `json:"headers"`         // raw headers (JSON)
	HTML          string `json:"html"`            // html body
	Text          string `json:"text"`            // text body
	Subject       string `json:"subject"`         // subject
	RawMessageURL string `json:"raw_message_url"` // .eml download URL (15-day TTL)
	RawMessage    string `json:"raw_message"`     // raw RFC 822 message
	Token         string `json:"token"`           // random 50-char string
	Signature     string `json:"signature"`       // signature string
	UserHeaders   string `json:"userHeaders"`     // custom SC-Custom-* headers
	Reference     string `json:"reference"`       // original SendCloud Message-ID
	EmailID       string `json:"emailId"`         // parent email ID
	LabelID       int    `json:"labelId"`         // parent label ID
	LabelName     string `json:"labelName"`       // parent label name
}

SendCloudInboundEvent is a parsed inbound (route) webhook payload from SendCloud, representing a reply email forwarded by a 收信路由.

func ParseSendCloudInboundEvent

func ParseSendCloudInboundEvent(data []byte) (*SendCloudInboundEvent, error)

ParseSendCloudInboundEvent parses a JSON or x-www-form-urlencoded SendCloud route webhook body into an inbound event.

func (*SendCloudInboundEvent) ToMailMessage

func (e *SendCloudInboundEvent) ToMailMessage() (*MailMessage, error)

ToMailMessage converts an inbound event into a MailMessage. If RawMessage is present it is parsed for full body/attachment extraction; otherwise the HTML/Text fields are used directly.

type SendCloudProvider

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

SendCloudProvider sends email via the SendCloud HTTP API.

func NewSendCloudProvider

func NewSendCloudProvider(cfg SendCloudConfig) (*SendCloudProvider, error)

NewSendCloudProvider constructs a SendCloudProvider from the given config.

func (*SendCloudProvider) Kind

func (p *SendCloudProvider) Kind() string

Kind returns "sendcloud".

func (*SendCloudProvider) SendHTMLWith

func (p *SendCloudProvider) SendHTMLWith(to, subject, htmlBody string, vars map[string]any) (string, error)

SendHTMLWith renders the subject and HTML body with vars and sends the message via the SendCloud API.

func (*SendCloudProvider) SendTextWith

func (p *SendCloudProvider) SendTextWith(to, subject, textBody string, vars map[string]any) (string, error)

SendTextWith renders the subject and text body with vars and sends the message via the SendCloud API.

type SendCloudReader

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

SendCloudReader queries the SendCloud emailStatus API for delivery records of previously-sent messages. It implements MailReader by mapping delivery records onto MailMessage structs (body is not available — only status metadata).

func NewSendCloudReader

func NewSendCloudReader(cfg SendCloudConfig) (*SendCloudReader, error)

NewSendCloudReader constructs a SendCloudReader from the given config. The API user/key must be set; From is not required for status queries.

func (*SendCloudReader) AddInboundRoute

func (r *SendCloudReader) AddInboundRoute(expression, action, apiUserRoute string) (int, error)

AddInboundRoute creates a new inbound route. expression is the matching pattern, e.g. "contact@email.lingecho.com" or ".*@email.lingecho.com" (regex@domain). action is the destination — either a webhook URL (e.g. "https://yourapp.com/webhook/sendcloud") or an email address to forward to (e.g. "you@qq.com"). When action is an email address, apiUserRoute must be set to the API_USER used for forwarding.

func (*SendCloudReader) Close

func (r *SendCloudReader) Close() error

Close releases the HTTP client. No persistent connection to maintain.

func (*SendCloudReader) DeleteInboundRoute

func (r *SendCloudReader) DeleteInboundRoute(routeID int) error

DeleteInboundRoute removes an inbound route by ID.

func (*SendCloudReader) DeleteMessage

func (r *SendCloudReader) DeleteMessage(id string) error

DeleteMessage is a no-op for SendCloud (delivery records cannot be deleted via this API).

func (*SendCloudReader) Kind

func (r *SendCloudReader) Kind() string

Kind returns "sendcloud".

func (*SendCloudReader) ListInboundRoutes

func (r *SendCloudReader) ListInboundRoutes(domain string, start, limit int) ([]SendCloudRoute, error)

ListInboundRoutes queries existing inbound routes.

func (*SendCloudReader) MarkRead

func (r *SendCloudReader) MarkRead(id string) error

MarkRead is a no-op for SendCloud (delivery records are read-only).

func (*SendCloudReader) QueryStatus

QueryStatus fetches delivery-status records matching the given query. Returns the records plus the total count reported by the API.

func (*SendCloudReader) ReadMessages

func (r *SendCloudReader) ReadMessages(limit int) ([]*MailMessage, error)

ReadMessages fetches up to `limit` recent delivery records and maps them onto MailMessage. The From field is set to the configured sender.

type SendCloudRoute

type SendCloudRoute struct {
	ID           int    `json:"id"`
	Domain       string `json:"domain"`
	Expression   string `json:"expression"`   // e.g. "reply@yourdomain.com" or ".*@yourdomain.com"
	Action       string `json:"action"`       // "URL" or "邮箱"
	APIUserRoute string `json:"apiUserRoute"` // required when action is email
}

SendCloudRoute is an inbound route configuration entry.

type SendCloudStatusQuery

type SendCloudStatusQuery struct {
	Email       string   // filter by recipient address
	EmailIDs    []string // filter by SendCloud email IDs (joined with ';')
	LabelID     string
	LabelName   string
	APIUserList []string // multiple apiUser filter (joined with ';')
	Days        int      // shortcut for "past N days" (1 = today); max 3
	StartDate   string   // yyyy-MM-dd; required if Days == 0
	EndDate     string   // yyyy-MM-dd; required if Days == 0
	Start       int      // offset, default 0
	Limit       int      // 0-100, default 100
	Status      string   // "1" delivered, "4" invalid, "5" soft-bounce, "18" requested
	SubStatus   string   // e.g. "401;406"
}

SendCloudStatusQuery holds the filters for a SendCloud emailStatus query. Either Days or (StartDate + EndDate) must be set; the query window cannot exceed 3 days.

type SendCloudWebhookEvent

type SendCloudWebhookEvent struct {
	Event      string `json:"event"`
	MessageID  string `json:"messageId"`
	Email      string `json:"email"`
	Timestamp  int64  `json:"timestamp"`
	SmtpStatus string `json:"smtpStatus"`
	SmtpError  string `json:"smtpError"`
}

SendCloudWebhookEvent is a normalized webhook payload from SendCloud. It can be received as JSON or x-www-form-urlencoded.

func ParseSendCloudWebhookEvent

func ParseSendCloudWebhookEvent(data []byte) (*SendCloudWebhookEvent, error)

ParseSendCloudWebhookEvent parses a JSON or x-www-form-urlencoded SendCloud webhook body into a normalized event.

Jump to

Keyboard shortcuts

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