package email import ( "context" "fmt" "sort" "time" ) const defaultSendTimeout = 10 * time.Second // Chain tries senders in ascending Sort order until one succeeds. type Chain struct { senders []Sender timeout time.Duration } type ChainOption func(*Chain) func WithTimeout(d time.Duration) ChainOption { return func(c *Chain) { c.timeout = d } } func NewChain(senders ...Sender) *Chain { c := &Chain{ senders: append([]Sender(nil), senders...), timeout: defaultSendTimeout, } c.sortSenders() return c } func NewChainWithOptions(senders []Sender, opts ...ChainOption) *Chain { c := &Chain{ senders: append([]Sender(nil), senders...), timeout: defaultSendTimeout, } for _, opt := range opts { opt(c) } c.sortSenders() return c } func (c *Chain) sortSenders() { sort.Slice(c.senders, func(i, j int) bool { return c.senders[i].Sort() < c.senders[j].Sort() }) } // Send returns the winning provider name and provider-assigned message id. func (c *Chain) Send(ctx context.Context, msg *Message) (providerName, messageID string, err error) { if len(c.senders) == 0 { return "", "", fmt.Errorf("email: no senders configured") } if msg == nil { return "", "", fmt.Errorf("email: message is nil") } sendCtx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() var lastErr error for _, sender := range c.senders { id, sendErr := sender.Send(sendCtx, msg) if sendErr == nil { return sender.Name(), id, nil } lastErr = sendErr } if lastErr == nil { lastErr = fmt.Errorf("email: all providers failed") } return "", "", fmt.Errorf("email: all providers failed: %w", lastErr) }