diff --git a/go.mod b/go.mod index 47d5f5a..c51873b 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,8 @@ require ( golang.org/x/net v0.30.0 ) +require github.com/xhit/go-simple-mail/v2 v2.16.0 + require ( code.as/core/socks v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -82,6 +84,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect github.com/writeas/slug v1.2.0 // indirect diff --git a/go.sum b/go.sum index 084e2c1..e9e3a6f 100644 --- a/go.sum +++ b/go.sum @@ -177,6 +177,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM= +github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= @@ -212,6 +214,8 @@ github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ5 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M= github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg= +github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= +github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/mailer/mailer.go b/mailer/mailer.go new file mode 100644 index 0000000..fbc4dd6 --- /dev/null +++ b/mailer/mailer.go @@ -0,0 +1,98 @@ +/* + * Copyright © 2024 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package mailer + +import ( + "fmt" + "github.com/mailgun/mailgun-go" + "github.com/writefreely/writefreely/config" + mail "github.com/xhit/go-simple-mail/v2" +) + +type ( + // Mailer holds configurations for the preferred mailing provider. + Mailer struct { + smtp *mail.SMTPServer + mailGun *mailgun.MailgunImpl + } + + // Message holds the email contents and metadata for the preferred mailing provider. + Message struct { + mgMsg *mailgun.Message + smtpMsg *mail.Email + } +) + +// New creates a new Mailer from the instance's config.EmailCfg, returning an error if not properly configured. +func New(eCfg *config.EmailCfg) (*Mailer, error) { + m := &Mailer{} + if eCfg.Domain != "" && eCfg.MailgunPrivate != "" { + m.mailGun = mailgun.NewMailgun(eCfg.Domain, eCfg.MailgunPrivate) + } else if eCfg.Username != "" && eCfg.Password != "" && eCfg.Host != "" && eCfg.Port > 0 { + m.smtp = mail.NewSMTPClient() + m.smtp.Host = eCfg.Host + m.smtp.Port = eCfg.Port + m.smtp.Username = eCfg.Username + m.smtp.Password = eCfg.Password + if eCfg.EnableStartTLS { + m.smtp.Encryption = mail.EncryptionSTARTTLS + } + } else { + return nil, fmt.Errorf("no email provider is configured") + } + + return m, nil +} + +// NewMessage creates a new Message from the given parameters. +func (m *Mailer) NewMessage(from, subject, text string, to ...string) (*Message, error) { + msg := &Message{} + if m.mailGun != nil { + msg.mgMsg = m.mailGun.NewMessage(from, subject, text, to...) + } else if m.smtp != nil { + msg.smtpMsg = mail.NewMSG() + msg.smtpMsg.SetFrom(from) + msg.smtpMsg.AddTo(to...) + msg.smtpMsg.SetSubject(subject) + msg.smtpMsg.AddAlternative(mail.TextPlain, text) + + if msg.smtpMsg.Error != nil { + return nil, msg.smtpMsg.Error + } + } + return msg, nil +} + +// SetHTML sets the body of the message. +func (m *Message) SetHTML(html string) { + if m.smtpMsg != nil { + m.smtpMsg.SetBody(mail.TextHTML, html) + } else if m.mgMsg != nil { + m.mgMsg.SetHtml(html) + } +} + +// Send sends the given message via the preferred provider. +func (m *Mailer) Send(msg *Message) error { + if m.smtp != nil { + client, err := m.smtp.Connect() + if err != nil { + return err + } + return msg.smtpMsg.Send(client) + } else if m.mailGun != nil { + _, _, err := m.mailGun.Send(msg.mgMsg) + if err != nil { + return err + } + } + return nil +}