Documentation ¶
Index ¶
- Constants
- func EmitPreviewMessageFromJson(input io.Reader, output io.Writer) error
- type AddressValidator
- type Bouncer
- type Mailer
- type Message
- type MessageTemplate
- type MessageValidatorFunc
- type ProdAddressValidator
- type Recipient
- type Resolver
- type SesApi
- type SesBouncer
- type SesMailer
- type SesSuppressor
- type SesThrottle
- type SesV2Api
- type Suppressor
- type Throttle
- type ValidationFailure
Constants ¶
const ErrBulkSendCapacityExhausted = types.SentinelError(
"Bulk capacity for 24 hour max send quota already consumed",
)
const ErrExceededMax24HourSend = types.SentinelError(
"Exceeded 24 hour maximum send quota",
)
const ExampleMessageJson = ` { "From": "Foo Bar <foobar@example.com>", "Subject": "Test object", "TextBody": "Hello, World!", "TextFooter": "Unsubscribe: ` + UnsubscribeUrlTemplate + `", "HtmlBody": "<!DOCTYPE html><html><head></head><body>Hello, World!<br/>", "HtmlFooter": "<a href='` + UnsubscribeUrlTemplate + `'>Unsubscribe</a></body></html>" }`
const UnsubscribeUrlTemplate = "{{UnsubscribeUrl}}"
Variables ¶
This section is empty.
Functions ¶
Types ¶
type AddressValidator ¶
type AddressValidator interface { ValidateAddress( ctx context.Context, email string, ) (failure *ValidationFailure, err error) }
AddressValidator wraps the ValidateAddress method.
ValidateAddress parses and validates email addresses. The intent is to reduce bounced emails and other potential abuse by filtering out bad addresses before attempting to send email to them.
The failure return value will be nil if the address passes validation, or non nil if it fails.
type Message ¶
var ExampleMessage *Message = MustParseMessageFromJson( strings.NewReader(ExampleMessageJson), )
func MustParseMessageFromJson ¶
func MustParseMessageFromJson( r io.Reader, validators ...MessageValidatorFunc, ) (msg *Message)
func NewMessageFromJson ¶
func NewMessageFromJson( r io.Reader, validators ...MessageValidatorFunc, ) (msg *Message, err error)
func (*Message) Validate ¶
func (msg *Message) Validate(validators ...MessageValidatorFunc) error
type MessageTemplate ¶
type MessageTemplate struct {
// contains filtered or unexported fields
}
func NewMessageTemplate ¶
func NewMessageTemplate(m *Message) *MessageTemplate
func NewMessageTemplateFromJson ¶
func NewMessageTemplateFromJson( r io.Reader, validators ...MessageValidatorFunc, ) (mt *MessageTemplate, err error)
func (*MessageTemplate) EmitMessage ¶
func (mt *MessageTemplate) EmitMessage(b io.Writer, r *Recipient) error
func (*MessageTemplate) GenerateMessage ¶
func (mt *MessageTemplate) GenerateMessage(r *Recipient) []byte
type MessageValidatorFunc ¶
MessageValidatorFunc is the interface for Message.Validate validators.
These functions are applied after all other Message.Validate checks, including the parsing of the From address. Validate passes the result of a successful parse via the fromName and fromAddress parameters.
func CheckDomain ¶
func CheckDomain(domain string) MessageValidatorFunc
CheckDomain ensures Message.From is from the expected domain.
type ProdAddressValidator ¶
type ProdAddressValidator struct { Suppressor Suppressor Resolver Resolver }
ProdAddressValidator is the production implementation of AddressValidator.
func (*ProdAddressValidator) ValidateAddress ¶
func (av *ProdAddressValidator) ValidateAddress( ctx context.Context, address string, ) (failure *ValidationFailure, err error)
ValidateAddress parses and validates email addresses.
If the address passes validation, the returned ValidationFailure and error values will both be nil. If a network or DNS error occurs, the returned error value will be non-nil and the ValidationFailure value will be nil. Absent such external errors, if the address fails validation, the ValidationFailure will be non-nil and the error will be nil.
This method:
- Parses the username and domain with the help of mail.ParseAddress
- Rejects known invalid usernames and domains
- Rejects addresses on the Simple Email Service account-level suppression list
- Looks up the DNS MX records (mail hosts) for the domain
- Confirms that at least one mail host is valid by examining DNS records
The mail host validation happens by iterating over each MX record until one satisfies the following series of checks:
- Resolve the MX record's hostname to an IP address
- Resolve the IP address to a hostname via reverse DNS lookup (depends on a DNS PTR record)
- Resolve that hostname to an IP address
- Check that two IP addresses match
Each of the lookups above can produce one or more addresses or hostnames. ValidateAddress will iterate through every one until a match is found, or return an error describing all failed attempts to find a match.
This algorithm was inspired by the "Reverse Entries for MX records" check from DNS Inspect. It's a pass-fast version of the following series of DNS lookups, except that it examines each address depth first and stops when one passes:
$ dig short -t mx mike-bland.com 10 inbound-smtp.us-east-1.amazonaws.com $ dig +short inbound-smtp.us-east-1.amazonaws.com 44.206.9.87 44.210.166.32 54.164.173.191 54.197.5.236 3.211.210.226 $ dig +short -x 44.206.9.87 -x 44.210.166.32 -x 54.164.173.191 \ -x 54.197.5.236 -x 3.211.210.226 ec2-44-206-9-87.compute-1.amazonaws.com. ec2-44-210-166-32.compute-1.amazonaws.com. ec2-54-164-173-191.compute-1.amazonaws.com. ec2-54-197-5-236.compute-1.amazonaws.com. ec2-3-211-210-226.compute-1.amazonaws.com. $ dig +short ec2-44-206-9-87.compute-1.amazonaws.com \ ec2-44-210-166-32.compute-1.amazonaws.com \ ec2-54-164-173-191.compute-1.amazonaws.com \ ec2-54-197-5-236.compute-1.amazonaws.com \ ec2-3-211-210-226.compute-1.amazonaws.com 44.206.9.87 44.210.166.32 54.164.173.191 54.197.5.236 3.211.210.226
Originally ValidateAddress was to implement the algorithm from How to Verify Email Address Without Sending an Email. The idea is to confirm the username exists for the email address domain before actually sending to it. However, most mail hosts these days don't allow anyone to dial in and ping mailboxes anymore, rendering this algorithm ineffective.
DNS validation of the domain and bounce notification handling in github.com/mbland/elistman/handler.Handler.HandleEvent should minimize the risk of bounces and abuse.
type Recipient ¶
func (*Recipient) EmitUnsubscribeHeaders ¶
func (*Recipient) FillInUnsubscribeUrl ¶
func (*Recipient) SetUnsubscribeInfo ¶
type Resolver ¶
type Resolver interface { LookupMX(ctx context.Context, name string) ([]*net.MX, error) LookupHost(ctx context.Context, host string) (addrs []string, err error) LookupAddr(ctx context.Context, addr string) (names []string, err error) }
Resolver wraps several methods from the net standard library.
This interface allows for unit testing code that relies on these methods without performing actual DNS lookups.
See net for documentation on these methods.
type SesApi ¶
type SesApi interface { SendBounce( context.Context, *ses.SendBounceInput, ...func(*ses.Options), ) (*ses.SendBounceOutput, error) }
type SesBouncer ¶
type SesBouncer struct {
Client SesApi
}
type SesMailer ¶
func (*SesMailer) BulkCapacityAvailable ¶
type SesSuppressor ¶
type SesSuppressor struct {
Client SesV2Api
}
func (*SesSuppressor) IsSuppressed ¶
func (*SesSuppressor) Suppress ¶
func (mailer *SesSuppressor) Suppress( ctx context.Context, email string, reason ops.RemoveReason, ) error
func (*SesSuppressor) Unsuppress ¶
func (mailer *SesSuppressor) Unsuppress( ctx context.Context, email string, ) error
type SesThrottle ¶
type SesThrottle struct { Client SesV2Api Updated time.Time PauseInterval time.Duration LastSend time.Time Sleep func(time.Duration) Now func() time.Time RefreshInterval time.Duration Max24HourSend int64 SentLast24Hours int64 MaxBulkCapacity types.Capacity MaxBulkSendable int64 }
func NewSesThrottle ¶
func (*SesThrottle) BulkCapacityAvailable ¶
func (t *SesThrottle) BulkCapacityAvailable(ctx context.Context) (err error)
func (*SesThrottle) PauseBeforeNextSend ¶
func (t *SesThrottle) PauseBeforeNextSend(ctx context.Context) (err error)
type SesV2Api ¶
type SesV2Api interface { GetSuppressedDestination( context.Context, *sesv2.GetSuppressedDestinationInput, ...func(*sesv2.Options), ) (*sesv2.GetSuppressedDestinationOutput, error) PutSuppressedDestination( context.Context, *sesv2.PutSuppressedDestinationInput, ...func(*sesv2.Options), ) (*sesv2.PutSuppressedDestinationOutput, error) DeleteSuppressedDestination( context.Context, *sesv2.DeleteSuppressedDestinationInput, ...func(*sesv2.Options), ) (*sesv2.DeleteSuppressedDestinationOutput, error) GetAccount( context.Context, *sesv2.GetAccountInput, ...func(*sesv2.Options), ) (*sesv2.GetAccountOutput, error) SendEmail( context.Context, *sesv2.SendEmailInput, ...func(*sesv2.Options), ) (*sesv2.SendEmailOutput, error) }
type Suppressor ¶
type Suppressor interface { // IsSuppressed checks whether an email address is on the SES account-level // suppression list. IsSuppressed(ctx context.Context, email string) (bool, error) // Suppress adds an email address to the SES account-level suppression list. Suppress(ctx context.Context, email string, reason ops.RemoveReason) error // Unsuppress removes an email address from the SES account-level // suppression list. Unsuppress(ctx context.Context, email string) error }
Suppressor wraps methods for the SES account-level suppression list.
type ValidationFailure ¶
func (*ValidationFailure) String ¶
func (vf *ValidationFailure) String() string