Files
remark42/backend/vendor/github.com/go-pkgz/notify/email.go
T
Dmitry VerkhoturovandUmputun cebe929118 bump go modules, enable LoginAuth option for email
That option is needed for outlook.com and Office 365, resolves #1472.
2022-10-03 03:26:34 -05:00

147 lines
3.9 KiB
Go

package notify
import (
"context"
"fmt"
"net/mail"
"net/url"
"strings"
"time"
"github.com/go-pkgz/email"
)
// SMTPParams contain settings for smtp server connection
type SMTPParams struct {
Host string // SMTP host
Port int // SMTP port
TLS bool // TLS auth
StartTLS bool // StartTLS auth
ContentType string // Content type
Charset string // Character set
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
Username string // username
Password string // password
TimeOut time.Duration // TCP connection timeout
}
// Email notifications client
type Email struct {
SMTPParams
sender *email.Sender
}
// NewEmail makes new Email object
func NewEmail(smtpParams SMTPParams) *Email {
var opts []email.Option
if smtpParams.Username != "" {
opts = append(opts, email.Auth(smtpParams.Username, smtpParams.Password))
}
if smtpParams.ContentType != "" {
opts = append(opts, email.ContentType(smtpParams.ContentType))
}
if smtpParams.Charset != "" {
opts = append(opts, email.Charset(smtpParams.Charset))
}
if smtpParams.LoginAuth {
opts = append(opts, email.LoginAuth())
}
if smtpParams.Port != 0 {
opts = append(opts, email.Port(smtpParams.Port))
}
if smtpParams.TimeOut != 0 {
opts = append(opts, email.TimeOut(smtpParams.TimeOut))
}
if smtpParams.TLS {
opts = append(opts, email.TLS(true))
}
if smtpParams.StartTLS {
opts = append(opts, email.STARTTLS(true))
}
sender := email.NewSender(smtpParams.Host, opts...)
return &Email{sender: sender, SMTPParams: smtpParams}
}
// Send sends the message over Email, with "from", "subject" and "unsubscribeLink" parsed from destination field
// with "mailto:" schema.
// "unsubscribeLink" passed as a header, https://support.google.com/mail/answer/81126 -> "Use one-click unsubscribe"
//
// Example:
//
// - mailto:"John Wayne"<john@example.org>?subject=test-subj&from="Notifier"<notify@example.org>
// - mailto:addr1@example.org,addr2@example.org?subject=test-subj&from=notify@example.org&unsubscribeLink=http://example.org/unsubscribe
func (e *Email) Send(ctx context.Context, destination, text string) error {
emailParams, err := e.parseDestination(destination)
if err != nil {
return fmt.Errorf("problem parsing destination: %w", err)
}
select {
case <-ctx.Done():
return ctx.Err()
default:
return e.sender.Send(text, emailParams)
}
}
// Schema returns schema prefix supported by this client
func (e *Email) Schema() string {
return "mailto"
}
// String representation of Email object
func (e *Email) String() string {
str := fmt.Sprintf("email: with username '%s' at server %s:%d", e.Username, e.Host, e.Port)
if e.TLS {
str += " with TLS"
}
if e.StartTLS {
str += " with StartTLS"
}
return str
}
// parses "mailto:" URL and returns email parameters
func (e *Email) parseDestination(destination string) (email.Params, error) {
// parse URL
u, err := url.Parse(destination)
if err != nil {
return email.Params{}, err
}
if u.Scheme != "mailto" {
return email.Params{}, fmt.Errorf("unsupported scheme %s, should be mailto", u.Scheme)
}
// parse destination address(es)
addresses, err := mail.ParseAddressList(u.Opaque)
if err != nil {
return email.Params{}, fmt.Errorf("problem parsing email recipients: %w", err)
}
destinations := []string{}
for _, addr := range addresses {
stringAddr := addr.String()
// in case of mailgun, correct RFC5322 address with <> yield 501 error, so we need to remove brackets
if strings.HasPrefix(stringAddr, "<") && strings.HasSuffix(stringAddr, ">") {
stringAddr = stringAddr[1 : len(stringAddr)-1]
}
destinations = append(destinations, stringAddr)
}
return email.Params{
From: u.Query().Get("from"),
To: destinations,
Subject: u.Query().Get("subject"),
UnsubscribeLink: u.Query().Get("unsubscribeLink"),
}, nil
}