email

package
v1.3.3 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TypeVerifyEmail          = "verify_email"
	TypeVerificationReminder = "verification_reminder"
	TypePasswordReset        = "password_reset"
	TypePasswordChanged      = "password_changed"
	TypeTwoFactorReset       = "two_factor_reset"
	TypeNotification         = "notification"
	TypeSignatureRequest     = "signature_request"
	TypeSignatureCompleted   = "signature_completed"
	TypeAdminTest            = "admin_test"
)

Message type identifiers used across the codebase. New senders should add a constant here rather than passing a literal, so the delivery log stays groupable.

Variables

View Source
var (
	// EmailSendTotal counts email send attempts by result.
	// Results: success, failure, dry_run, invalid_address.
	EmailSendTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "email_send_total",
			Help: "Total number of email send attempts.",
		},
		[]string{"result"},
	)

	// EmailDeliveryTotal breaks send attempts down by what the SMTP
	// conversation actually said, and for which kind of message.
	//
	// It sits alongside EmailSendTotal rather than replacing it: the coarse
	// counter above is what existing dashboards and alert rules are written
	// against, while this one is what tells a hard bounce (a dead address)
	// apart from a server error (a broken mail setup).
	//
	// Statuses: delivered, hard_bounce, soft_bounce, rejected, invalid_address,
	// suppressed, server_error, dry_run.
	EmailDeliveryTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "email_delivery_total",
			Help: "Email send attempts by message type and delivery outcome.",
		},
		[]string{"type", "status"},
	)

	// EmailSuppressedAddresses is the number of addresses currently refused
	// because they hard-bounced. A rising line means real users are losing
	// mail.
	EmailSuppressedAddresses = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name: "email_suppressed_addresses",
			Help: "Number of email addresses suppressed after a permanent delivery failure.",
		},
	)

	// EmailSendDurationSeconds tracks SMTP send latency for delivered emails.
	EmailSendDurationSeconds = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "email_send_duration_seconds",
			Help:    "Latency of the SMTP send call in seconds (both successful and failed attempts).",
			Buckets: prometheus.DefBuckets,
		},
	)
)

Functions

func Templates

func Templates(locale string) templateSet

Templates returns the email template functions for the given locale. Falls back to English if locale is not supported.

Types

type Attempt added in v1.3.2

type Attempt struct {
	Recipient string
	Type      string
	Status    DeliveryStatus
	// Code is the SMTP reply code, 0 when there was no conversation.
	Code int
	// Detail is the server's reply text or our own reason, for an operator to
	// read. It is never shown to the recipient.
	Detail string
}

Attempt is one row of the delivery log.

type CredentialExpiryParams

type CredentialExpiryParams struct {
	UserName       string
	CredentialType string
	ExpiryDate     string
	DaysRemaining  int
}

type CustomCurrencyParams added in v1.3.1

type CustomCurrencyParams struct {
	UserName string
	RuleName string
	// Expiring is true for a "will lapse soon" warning (ExpiresOn set), false
	// for a "no longer current" notice.
	Expiring  bool
	ExpiresOn string
}

type DeliveryRecorder added in v1.3.2

type DeliveryRecorder interface {
	RecordAttempt(ctx context.Context, a Attempt)
	// IsSuppressed reports whether the address has hard-bounced before. It is
	// consulted before dialling, so implementations should fail open (return
	// false) if they cannot answer — refusing to send because a lookup failed
	// would be worse than one wasted delivery attempt.
	IsSuppressed(ctx context.Context, recipient string) bool
}

DeliveryRecorder persists what happened to each send and answers whether an address has been given up on. It is an interface so pkg/email stays free of database dependencies; the implementation lives in internal/service.

RecordAttempt returns nothing on purpose: failing to write an audit row must never turn a delivered email into a reported failure. Implementations log their own storage errors.

type DeliveryStatus added in v1.3.2

type DeliveryStatus string

DeliveryStatus is what one send attempt turned out to be. It is deliberately narrow: an SMTP client only ever learns about delivery during the SMTP conversation, so these are the only outcomes we can honestly claim.

const (
	// StatusDelivered means the receiving server accepted the message. It is
	// not a promise that the message reached an inbox — a server that accepts
	// and then bounces asynchronously (which most do) is invisible from here.
	StatusDelivered DeliveryStatus = "delivered"

	// StatusHardBounce means the recipient was refused permanently: a 5xx reply
	// to RCPT TO or to the message data. The mailbox does not exist, or the
	// server will never take mail for it. Retrying the same address is futile.
	StatusHardBounce DeliveryStatus = "hard_bounce"

	// StatusSoftBounce is a temporary refusal of the recipient (4xx): greylisting,
	// a full mailbox, a server over quota. Worth retrying later.
	StatusSoftBounce DeliveryStatus = "soft_bounce"

	// StatusInvalidAddress means the address failed to parse, so nothing was
	// dialled. Permanent, but our judgement rather than the server's.
	StatusInvalidAddress DeliveryStatus = "invalid_address"

	// StatusSuppressed means we declined to send because the address is on the
	// suppression list from an earlier hard bounce.
	StatusSuppressed DeliveryStatus = "suppressed"

	// StatusRejected means the server took the recipient but refused the
	// message itself, after the data was sent — a size limit, a content or
	// reputation policy. It says nothing about whether the mailbox exists, so
	// it must never suppress the address.
	StatusRejected DeliveryStatus = "rejected"

	// StatusServerError covers every failure that is about our side or the
	// connection rather than the recipient: DNS, refused connections, TLS
	// failures, bad SMTP credentials. Keeping these distinct is the whole point
	// of classifying per command — a 535 "authentication failed" is a 5xx, and
	// treating it as a bounce would suppress every address the instant SMTP
	// credentials went stale.
	StatusServerError DeliveryStatus = "server_error"

	// StatusDryRun records a send skipped because SMTP is not configured.
	StatusDryRun DeliveryStatus = "dry_run"
)

func (DeliveryStatus) IsRecipientFailure added in v1.3.2

func (s DeliveryStatus) IsRecipientFailure() bool

IsRecipientFailure reports whether the status blames the address rather than our infrastructure. Only these justify giving up on an address.

type FlightReviewExpiryParams

type FlightReviewExpiryParams struct {
	UserName      string
	ExpiryDate    string
	DaysRemaining int
}

type FlightReviewRequiredParams

type FlightReviewRequiredParams struct {
	UserName string
	Message  string
}

type Message added in v1.3.2

type Message struct {
	To       string
	Subject  string
	HTMLBody string
	Type     string
}

Message is one outbound email. Type is a short stable identifier for what the message was for ("verify_email", "verification_reminder", …) so the delivery log can be read by purpose.

type PassengerCurrencyParams

type PassengerCurrencyParams struct {
	UserName  string
	ClassType string
	Landings  int
	Required  int
	Remaining int
	Period    string // "day" or "night"
}

type PasswordChangedParams added in v1.3.2

type PasswordChangedParams struct {
	UserName string
	// TwoFactorEnabled reports whether 2FA is still active on the account, so
	// the notice can state plainly that the second factor was not removed.
	TwoFactorEnabled bool
}

PasswordChangedParams drives the security notice sent after a password reset completes. It is what tells the account owner that the reset happened at all.

type PasswordResetParams added in v1.3.2

type PasswordResetParams struct {
	UserName string
	Link     string
	// TwoFactorEnabled warns the user up front that finishing the reset also
	// requires their authenticator code or a recovery code.
	TwoFactorEnabled bool
}

type RatingExpiryParams

type RatingExpiryParams struct {
	UserName      string
	LicenseType   string
	ClassType     string
	ExpiryDate    string
	DaysRemaining int
}

type RevalidationParams

type RevalidationParams struct {
	UserName    string
	LicenseType string
	ClassType   string
	Message     string
}

type SMTPConfig

type SMTPConfig struct {
	Host     string
	Port     string
	Username string
	Password string
	From     string
}

SMTPConfig holds SMTP server configuration

func LoadSMTPConfig

func LoadSMTPConfig() *SMTPConfig

LoadSMTPConfig loads SMTP configuration from environment variables

func (*SMTPConfig) IsConfigured

func (c *SMTPConfig) IsConfigured() bool

IsConfigured returns true if SMTP is properly configured

type SendError added in v1.3.2

type SendError struct {
	Status DeliveryStatus
	// Code is the SMTP reply code when the server gave one, 0 otherwise.
	Code int
	Err  error
}

SendError carries the classification alongside the underlying failure so a caller can tell "this address is dead" from "our mail server is down" without re-parsing SMTP replies.

func (*SendError) Error added in v1.3.2

func (e *SendError) Error() string

func (*SendError) Permanent added in v1.3.2

func (e *SendError) Permanent() bool

Permanent reports whether retrying this address could ever succeed. A server error is not permanent — the same address will go through once the server or the credentials are fixed.

func (*SendError) Unwrap added in v1.3.2

func (e *SendError) Unwrap() error

type Sender

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

Sender sends emails via SMTP

func NewSender

func NewSender(config *SMTPConfig) *Sender

NewSender creates a new SMTP email sender

func (*Sender) IsConfigured added in v1.2.6

func (s *Sender) IsConfigured() bool

IsConfigured returns true when the sender has a usable SMTP configuration.

func (*Sender) Send

func (s *Sender) Send(to, subject, htmlBody string) error

Send sends an email without delivery classification context. It exists for callers that have no context.Context and no meaningful message type; new call sites should use SendMessage so the delivery log stays readable.

func (*Sender) SendMessage added in v1.3.2

func (s *Sender) SendMessage(ctx context.Context, msg Message) error

SendMessage sends an email and records what the SMTP conversation said about it.

The recipient is validated with net/mail.ParseAddress (which rejects CR/LF and other header-injection vectors) and is delivered only through the SMTP envelope (the RCPT TO argument). The user-controlled recipient is never concatenated into the message DATA headers, so it cannot be used to inject additional headers (CWE-640). The subject is MIME Q-encoded so any non-ASCII or control bytes are escaped and cannot break out of the Subject header.

A returned error is always a *SendError, so callers can distinguish a dead address from a broken mail server with errors.As.

func (*Sender) SetDeliveryRecorder added in v1.3.2

func (s *Sender) SetDeliveryRecorder(r DeliveryRecorder)

SetDeliveryRecorder attaches the delivery log. It is a setter rather than a constructor argument because the recorder is backed by a repository, and the sender is built before the database layer in cmd/api/main.go. A sender without a recorder still sends; it just keeps no history.

type SignatureCompletedParams added in v1.2.9

type SignatureCompletedParams struct {
	OwnerName     string
	FlightSummary string
	SignerName    string
}

type SignatureRequestParams added in v1.2.9

type SignatureRequestParams struct {
	OwnerName string
	// OwnerEmail is the requester's VERIFIED account address. OwnerName is a
	// free-form display name the sender chooses, so on its own it lets anyone
	// address a stranger as e.g. "Lufthansa Flight Operations" from the
	// platform's own domain. Showing the account address alongside it gives the
	// recipient something the sender could not invent.
	OwnerEmail    string
	FlightSummary string // e.g. "12 Jul 2026 — D-EFGH (C172), 1h24m"
	Link          string
	ExpiresAt     string
}

type TwoFactorResetParams added in v1.3.2

type TwoFactorResetParams struct {
	UserName string
}

TwoFactorResetParams drives the notice sent when an administrator clears a user's 2FA enrolment.

type VerificationReminderParams added in v1.3.2

type VerificationReminderParams struct {
	UserName string
	Link     string
	// DeletionDays is how long the account survives from this reminder.
	DeletionDays int
	// LinkValidDays is how long the enclosed link works, which is shorter.
	LinkValidDays int
}

VerificationReminderParams drives the follow-up sent when an account is still unverified a day after signup. It states the deletion deadline outright: the account will be removed if the address is never confirmed, and someone who never sees that stated has no way to act on it.

type VerifyEmailParams added in v1.2.5

type VerifyEmailParams struct {
	UserName string
	Link     string
}

Jump to

Keyboard shortcuts

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