Documentation
¶
Index ¶
- Constants
- Variables
- func Templates(locale string) templateSet
- type Attempt
- type CredentialExpiryParams
- type CustomCurrencyParams
- type DeliveryRecorder
- type DeliveryStatus
- type FlightReviewExpiryParams
- type FlightReviewRequiredParams
- type Message
- type PassengerCurrencyParams
- type PasswordChangedParams
- type PasswordResetParams
- type RatingExpiryParams
- type RevalidationParams
- type SMTPConfig
- type SendError
- type Sender
- type SignatureCompletedParams
- type SignatureRequestParams
- type TwoFactorResetParams
- type VerificationReminderParams
- type VerifyEmailParams
Constants ¶
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 ¶
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 ¶
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 CustomCurrencyParams ¶ added in v1.3.1
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 Message ¶ added in v1.3.2
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 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 RatingExpiryParams ¶
type RevalidationParams ¶
type SMTPConfig ¶
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.
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
IsConfigured returns true when the sender has a usable SMTP configuration.
func (*Sender) Send ¶
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
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 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.