package console import ( "context" "errors" "log" "strings" "time" ) // deliveryChannel names how deliver got a message out. type deliveryChannel string const ( deliveredKeymail deliveryChannel = "keymail" deliveredEmail deliveryChannel = "email" ) // domainOf is the lowercased part of addr after its last "@", "" if none. func domainOf(addr string) string { i := strings.LastIndex(addr, "@") if i < 0 { return "" } return strings.ToLower(addr[i+1:]) } // deliver sends one message to an address the person did not just type (an // invite, a notice): Keymail federation first, then the console mailer (SES). // The fall-through rule (spec 4.3): // // - a definite "no mailbox" (ErrNoKeymailMailbox) goes to the mailer; // - an unreachable lookup stops when the address is at the Keymail host // (it IS a Keymail mailbox, so SES must not be used), and otherwise goes // to the mailer; // - a block, a price above the cap, or any failure after a successful // lookup stops with that error: the address is a Keymail mailbox. // // keymailShare bounds Keymail's part of ctx so the mailer keeps the rest. // On error the channel is "". func (s *Server) deliver(ctx context.Context, to, subject, body string, keymailShare time.Duration) (deliveryChannel, error) { if s.sendFederated != nil { kctx, cancel := context.WithTimeout(ctx, keymailShare) _, err := s.sendFederated(kctx, to, subject, body) cancel() if err == nil { return deliveredKeymail, nil } switch { case IsKeymailBlocked(err): // The recipient blocked the console. That is their answer; // email to the same address would go round it. log.Printf("console: delivery to %s: the recipient blocked the console on keymail; not emailing", to) return "", err case errors.Is(err, ErrNoKeymailMailbox): log.Printf("console: delivery to %s: no keymail mailbox, trying email", to) case errors.Is(err, ErrKeymailUnreachable): if s.km != nil && domainOf(to) == s.km.Host() { log.Printf("console: delivery to %s: keymail lookup unreachable for a keymail address; not emailing: %v", to, err) return "", err } log.Printf("console: delivery to %s: keymail lookup unreachable, trying email: %v", to, err) default: // ErrFederationPriceTooHigh, or a failure after the lookup // said the mailbox exists. log.Printf("console: delivery to %s by keymail: %v", to, err) return "", err } } if s.consoleMail == nil { return "", errors.New("console: no email sender is configured") } if err := s.consoleMail.Send(ctx, to, subject, body); err != nil { log.Printf("console: delivery to %s by email: %v", to, err) return "", err } return deliveredEmail, nil }