email

package
v0.0.0-...-25a6cea Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2023 License: MPL-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const ErrBulkSendCapacityExhausted = types.SentinelError(
	"Bulk capacity for 24 hour max send quota already consumed",
)
View Source
const ErrExceededMax24HourSend = types.SentinelError(
	"Exceeded 24 hour maximum send quota",
)
View Source
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>"
  }`
View Source
const UnsubscribeUrlTemplate = "{{UnsubscribeUrl}}"

Variables

This section is empty.

Functions

func EmitPreviewMessageFromJson

func EmitPreviewMessageFromJson(input io.Reader, output io.Writer) error

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 Bouncer

type Bouncer interface {
	Bounce(
		ctx context.Context,
		emailDomain,
		messageId string,
		recipients []string,
		timestamp time.Time,
	) (string, error)
}

type Mailer

type Mailer interface {
	BulkCapacityAvailable(ctx context.Context) error

	Send(
		ctx context.Context, recipient string, msg []byte,
	) (messageId string, err error)
}

type Message

type Message struct {
	From       string
	Subject    string
	TextBody   string
	TextFooter string
	HtmlBody   string
	HtmlFooter string
}

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

type MessageValidatorFunc func(msg *Message, fromName, fromAddress string) error

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

type Recipient struct {
	Email string
	Uid   uuid.UUID
	// contains filtered or unexported fields
}
var ExampleRecipient *Recipient = func() (r *Recipient) {
	r = &Recipient{
		Email: "subscriber@foo.com",
		Uid:   uuid.MustParse("00000000-1111-2222-3333-444444444444"),
	}
	r.SetUnsubscribeInfo(
		"unsubscribe@bar.com",
		"https://bar.com/unsubscribe",
		"https://bar.com/email/",
	)
	return
}()

func (*Recipient) EmitUnsubscribeHeaders

func (sub *Recipient) EmitUnsubscribeHeaders(w io.Writer) (err error)

func (*Recipient) FillInUnsubscribeUrl

func (sub *Recipient) FillInUnsubscribeUrl(msg []byte) []byte

func (*Recipient) SetUnsubscribeInfo

func (sub *Recipient) SetUnsubscribeInfo(email, formUrl, apiBaseUrl string)

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
}

func (*SesBouncer) Bounce

func (mailer *SesBouncer) Bounce(
	ctx context.Context,
	emailDomain,
	messageId string,
	recipients []string,
	timestamp time.Time,
) (bounceMessageId string, err error)

https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda-example-functions.html

type SesMailer

type SesMailer struct {
	Client    SesV2Api
	ConfigSet string
	Throttle  Throttle
}

func (*SesMailer) BulkCapacityAvailable

func (mailer *SesMailer) BulkCapacityAvailable(ctx context.Context) error

func (*SesMailer) Send

func (mailer *SesMailer) Send(
	ctx context.Context, recipient string, msg []byte,
) (messageId string, err error)

type SesSuppressor

type SesSuppressor struct {
	Client SesV2Api
}

func (*SesSuppressor) IsSuppressed

func (mailer *SesSuppressor) IsSuppressed(
	ctx context.Context, email string,
) (verdict bool, err error)

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 NewSesThrottle(
	ctx context.Context,
	client SesV2Api,
	maxCap types.Capacity,
	sleep func(time.Duration),
	now func() time.Time,
	refreshInterval time.Duration,
) (t *SesThrottle, err error)

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 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 Throttle

type Throttle interface {
	BulkCapacityAvailable(ctx context.Context) error
	PauseBeforeNextSend(context.Context) error
}

type ValidationFailure

type ValidationFailure struct {
	Address string
	Reason  string
}

func (*ValidationFailure) String

func (vf *ValidationFailure) String() string

Jump to

Keyboard shortcuts

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