pop/email.go
2023-07-31 10:32:02 -04:00

188 lines
4.3 KiB
Go

package main
import (
"bytes"
"crypto/tls"
"errors"
"os"
"path/filepath"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/resendlabs/resend-go"
mail "github.com/xhit/go-simple-mail/v2"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
renderer "github.com/yuin/goldmark/renderer/html"
)
// ToSeparator is the separator used to split the To, Cc, and Bcc fields.
const ToSeparator = ","
// sendEmailSuccessMsg is the tea.Msg handled by Bubble Tea when the email has
// been sent successfully.
type sendEmailSuccessMsg struct{}
// sendEmailFailureMsg is the tea.Msg handled by Bubble Tea when the email has
// failed to send.
type sendEmailFailureMsg error
// sendEmailCmd returns a tea.Cmd that sends the email.
func (m Model) sendEmailCmd() tea.Cmd {
return func() tea.Msg {
attachments := make([]string, len(m.Attachments.Items()))
for i, a := range m.Attachments.Items() {
attachments[i] = a.FilterValue()
}
var err error
to := strings.Split(m.To.Value(), ToSeparator)
cc := strings.Split(m.Cc.Value(), ToSeparator)
bcc := strings.Split(m.Bcc.Value(), ToSeparator)
switch m.DeliveryMethod {
case SMTP:
err = sendSMTPEmail(to, cc, bcc, m.From.Value(), m.Subject.Value(), m.Body.Value(), attachments)
case Resend:
err = sendResendEmail(to, cc, bcc, m.From.Value(), m.Subject.Value(), m.Body.Value(), attachments)
default:
err = errors.New("[ERROR]: unknown delivery method")
}
if err != nil {
return sendEmailFailureMsg(err)
}
return sendEmailSuccessMsg{}
}
}
const gmailSuffix = "@gmail.com"
const gmailSMTPHost = "smtp.gmail.com"
const gmailSMTPPort = 587
func sendSMTPEmail(to, cc, bcc []string, from, subject, body string, attachments []string) error {
server := mail.NewSMTPClient()
var err error
server.Username = smtpUsername
server.Password = smtpPassword
server.Host = smtpHost
server.Port = smtpPort
// Set defaults for gmail.
if strings.HasSuffix(server.Username, gmailSuffix) {
if server.Port == 0 {
server.Port = gmailSMTPPort
}
if server.Host == "" {
server.Host = gmailSMTPHost
}
}
switch strings.ToLower(smtpEncryption) {
case "ssl":
server.Encryption = mail.EncryptionSSLTLS
case "none":
server.Encryption = mail.EncryptionNone
default:
server.Encryption = mail.EncryptionSTARTTLS
}
server.KeepAlive = false
server.ConnectTimeout = 10 * time.Second
server.SendTimeout = 10 * time.Second
server.TLSConfig = &tls.Config{
InsecureSkipVerify: smtpInsecureSkipVerify, //nolint:gosec
ServerName: server.Host,
}
smtpClient, err := server.Connect()
if err != nil {
return err
}
email := mail.NewMSG()
email.SetFrom(from).
AddTo(to...).
AddCc(cc...).
AddBcc(bcc...).
SetSubject(subject)
html := bytes.NewBufferString("")
convertErr := goldmark.Convert([]byte(body), html)
if convertErr != nil {
email.SetBody(mail.TextPlain, body)
} else {
email.SetBody(mail.TextHTML, html.String())
}
for _, a := range attachments {
email.Attach(&mail.File{
FilePath: a,
Name: filepath.Base(a),
})
}
return email.Send(smtpClient)
}
func sendResendEmail(to, cc, bcc []string, from, subject, body string, attachments []string) error {
client := resend.NewClient(resendAPIKey)
html := bytes.NewBufferString("")
// If the conversion fails, we'll simply send the plain-text body.
if unsafe {
markdown := goldmark.New(
goldmark.WithRendererOptions(
renderer.WithUnsafe(),
),
goldmark.WithExtensions(
extension.Strikethrough,
extension.Table,
extension.Linkify,
),
)
_ = markdown.Convert([]byte(body), html)
} else {
_ = goldmark.Convert([]byte(body), html)
}
request := &resend.SendEmailRequest{
From: from,
To: to,
Cc: cc,
Bcc: bcc,
Subject: subject,
Html: html.String(),
Text: body,
Attachments: makeAttachments(attachments),
}
_, err := client.Emails.Send(request)
if err != nil {
return err
}
return nil
}
func makeAttachments(paths []string) []resend.Attachment {
if len(paths) == 0 {
return nil
}
attachments := make([]resend.Attachment, len(paths))
for i, a := range paths {
f, err := os.ReadFile(a)
if err != nil {
continue
}
attachments[i] = resend.Attachment{
Content: string(f),
Filename: filepath.Base(a),
}
}
return attachments
}