Merge pull request #1131 from writefreely/smtp
Support SMTP email configuration
This commit is contained in:
commit
79ab0a3786
7 changed files with 251 additions and 33 deletions
33
account.go
33
account.go
|
@ -13,7 +13,7 @@ package writefreely
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mailgun/mailgun-go"
|
||||
"github.com/writefreely/writefreely/mailer"
|
||||
"github.com/writefreely/writefreely/spam"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
@ -1378,13 +1378,19 @@ func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) e
|
|||
|
||||
func emailPasswordReset(app *App, toEmail, token string) error {
|
||||
// Send email
|
||||
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||
mlr, err := mailer.New(app.cfg.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email."
|
||||
|
||||
plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)
|
||||
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.AddTag("Password Reset")
|
||||
m.SetHtml(fmt.Sprintf(`<html>
|
||||
m.SetHTML(fmt.Sprintf(`<html>
|
||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
||||
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
||||
|
@ -1394,8 +1400,7 @@ func emailPasswordReset(app *App, toEmail, token string) error {
|
|||
</div>
|
||||
</body>
|
||||
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara))
|
||||
_, _, err := gun.Send(m)
|
||||
return err
|
||||
return mlr.Send(m)
|
||||
}
|
||||
|
||||
func loginViaEmail(app *App, alias, redirectTo string) error {
|
||||
|
@ -1424,15 +1429,21 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
|
|||
}
|
||||
|
||||
// Send email
|
||||
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||
mlr, err := mailer.New(app.cfg.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toEmail := u.EmailClear(app.keys)
|
||||
footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email."
|
||||
|
||||
plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara)
|
||||
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.AddTag("Email Login")
|
||||
|
||||
m.SetHtml(fmt.Sprintf(`<html>
|
||||
m.SetHTML(fmt.Sprintf(`<html>
|
||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
||||
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
||||
|
@ -1441,9 +1452,7 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
|
|||
</div>
|
||||
</body>
|
||||
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara))
|
||||
_, _, err = gun.Send(m)
|
||||
|
||||
return err
|
||||
return mlr.Send(m)
|
||||
}
|
||||
|
||||
func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error {
|
||||
|
|
14
app.go
14
app.go
|
@ -429,15 +429,11 @@ func Initialize(apper Apper, debug bool) (*App, error) {
|
|||
|
||||
initActivityPub(apper.App())
|
||||
|
||||
if apper.App().cfg.Email.Domain != "" || apper.App().cfg.Email.MailgunPrivate != "" {
|
||||
if apper.App().cfg.Email.Domain == "" {
|
||||
log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.")
|
||||
} else if apper.App().cfg.Email.MailgunPrivate == "" {
|
||||
log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.")
|
||||
} else {
|
||||
log.Info("Starting publish jobs queue...")
|
||||
go startPublishJobsQueue(apper.App())
|
||||
}
|
||||
if apper.App().cfg.Email.Enabled() {
|
||||
log.Info("Starting publish jobs queue...")
|
||||
go startPublishJobsQueue(apper.App())
|
||||
} else {
|
||||
log.Error("[FAILED] Starting publish jobs queue: no email provider is configured.")
|
||||
}
|
||||
|
||||
// Handle local timeline, if enabled
|
||||
|
|
|
@ -171,8 +171,17 @@ type (
|
|||
}
|
||||
|
||||
EmailCfg struct {
|
||||
// SMTP configuration values
|
||||
Host string `ini:"smtp_host"`
|
||||
Port int `ini:"smtp_port"`
|
||||
Username string `ini:"smtp_username"`
|
||||
Password string `ini:"smtp_password"`
|
||||
EnableStartTLS bool `ini:"smtp_enable_start_tls"`
|
||||
|
||||
// Mailgun configuration values
|
||||
Domain string `ini:"domain"`
|
||||
MailgunPrivate string `ini:"mailgun_private"`
|
||||
MailgunEurope bool `ini:"mailgun_europe"`
|
||||
}
|
||||
|
||||
// Config holds the complete configuration for running a writefreely instance
|
||||
|
@ -242,7 +251,8 @@ func (ac *AppCfg) LandingPath() string {
|
|||
}
|
||||
|
||||
func (lc EmailCfg) Enabled() bool {
|
||||
return lc.Domain != "" && lc.MailgunPrivate != ""
|
||||
return (lc.Domain != "" && lc.MailgunPrivate != "") ||
|
||||
lc.Username != "" && lc.Password != "" && lc.Host != "" && lc.Port > 0
|
||||
}
|
||||
|
||||
func (ac AppCfg) SignupPath() string {
|
||||
|
|
37
email.go
37
email.go
|
@ -14,6 +14,7 @@ import (
|
|||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/writefreely/writefreely/mailer"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -21,7 +22,6 @@ import (
|
|||
|
||||
"github.com/aymerick/douceur/inliner"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/mailgun-go"
|
||||
stripmd "github.com/writeas/go-strip-markdown/v2"
|
||||
"github.com/writeas/impart"
|
||||
"github.com/writeas/web-core/data"
|
||||
|
@ -307,8 +307,14 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca
|
|||
|
||||
Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%`
|
||||
|
||||
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||
m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
|
||||
mlr, err := mailer.New(app.cfg.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := mlr.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
|
||||
if replyTo != "" {
|
||||
m.SetReplyTo(replyTo)
|
||||
|
@ -405,13 +411,13 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
|
|||
return err
|
||||
}
|
||||
|
||||
m.SetHtml(html)
|
||||
m.SetHTML(html)
|
||||
|
||||
log.Info("[email] Adding %d recipient(s)", len(subs))
|
||||
for _, s := range subs {
|
||||
e := s.FinalEmail(app.keys)
|
||||
log.Info("[email] Adding %s", e)
|
||||
err = m.AddRecipientAndVariables(e, map[string]interface{}{
|
||||
err = m.AddRecipientAndVariables(e, map[string]string{
|
||||
"id": s.ID,
|
||||
"to": e,
|
||||
"token": s.Token,
|
||||
|
@ -421,8 +427,8 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
|
|||
}
|
||||
}
|
||||
|
||||
res, _, err := gun.Send(m)
|
||||
log.Info("[email] Send result: %s", res)
|
||||
err = mlr.Send(m)
|
||||
log.Info("[email] Email sent")
|
||||
if err != nil {
|
||||
log.Error("Unable to send post email: %v", err)
|
||||
return err
|
||||
|
@ -437,17 +443,23 @@ func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) er
|
|||
}
|
||||
|
||||
// Send email
|
||||
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||
mlr, err := mailer.New(app.cfg.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser):
|
||||
|
||||
` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + `
|
||||
|
||||
If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.`
|
||||
m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
|
||||
m, err := mlr.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.AddTag("Email Verification")
|
||||
|
||||
m.SetHtml(`<html>
|
||||
m.SetHTML(`<html>
|
||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||
<div style="font-size: 1.2em;">
|
||||
<p>Confirm your subscription to <a href="` + c.CanonicalURL() + `">` + c.DisplayTitle() + `</a> to start receiving future posts:</p>
|
||||
|
@ -456,7 +468,10 @@ If you didn't subscribe to this site or you're not sure why you're getting this
|
|||
</div>
|
||||
</body>
|
||||
</html>`)
|
||||
gun.Send(m)
|
||||
err = mlr.Send(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
3
go.mod
3
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
|
||||
|
|
4
go.sum
4
go.sum
|
@ -178,6 +178,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=
|
||||
|
@ -213,6 +215,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=
|
||||
|
|
181
mailer/mailer.go
Normal file
181
mailer/mailer.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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/writeas/web-core/log"
|
||||
"github.com/writefreely/writefreely/config"
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 *SmtpMessage
|
||||
}
|
||||
|
||||
SmtpMessage struct {
|
||||
from string
|
||||
replyTo string
|
||||
subject string
|
||||
recipients []Recipient
|
||||
html string
|
||||
text string
|
||||
}
|
||||
|
||||
Recipient struct {
|
||||
email string
|
||||
vars map[string]string
|
||||
}
|
||||
)
|
||||
|
||||
// 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)
|
||||
if eCfg.MailgunEurope {
|
||||
m.mailGun.SetAPIBase("https://api.eu.mailgun.net/v3")
|
||||
}
|
||||
} 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
|
||||
}
|
||||
// To allow sending multiple email
|
||||
m.smtp.KeepAlive = true
|
||||
} 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 = &SmtpMessage{
|
||||
from: from,
|
||||
replyTo: "",
|
||||
subject: subject,
|
||||
recipients: make([]Recipient, len(to)),
|
||||
html: "",
|
||||
text: text,
|
||||
}
|
||||
for _, r := range to {
|
||||
msg.smtpMsg.recipients = append(msg.smtpMsg.recipients, Recipient{r, make(map[string]string)})
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// SetHTML sets the body of the message.
|
||||
func (m *Message) SetHTML(html string) {
|
||||
if m.smtpMsg != nil {
|
||||
m.smtpMsg.html = html
|
||||
} else if m.mgMsg != nil {
|
||||
m.mgMsg.SetHtml(html)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) SetReplyTo(replyTo string) {
|
||||
if m.smtpMsg != nil {
|
||||
m.smtpMsg.replyTo = replyTo
|
||||
} else {
|
||||
m.mgMsg.SetReplyTo(replyTo)
|
||||
}
|
||||
}
|
||||
|
||||
// AddTag attaches a tag to the Message for providers that support it.
|
||||
func (m *Message) AddTag(tag string) {
|
||||
if m.mgMsg != nil {
|
||||
m.mgMsg.AddTag(tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) AddRecipientAndVariables(r string, vars map[string]string) error {
|
||||
if m.smtpMsg != nil {
|
||||
m.smtpMsg.recipients = append(m.smtpMsg.recipients, Recipient{r, vars})
|
||||
return nil
|
||||
} else {
|
||||
varsInterfaces := make(map[string]interface{}, len(vars))
|
||||
for k, v := range vars {
|
||||
varsInterfaces[k] = v
|
||||
}
|
||||
return m.mgMsg.AddRecipientAndVariables(r, varsInterfaces)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
emailSent := false
|
||||
for _, r := range msg.smtpMsg.recipients {
|
||||
customMsg := mail.NewMSG()
|
||||
customMsg.SetFrom(msg.smtpMsg.from)
|
||||
if msg.smtpMsg.replyTo != "" {
|
||||
customMsg.SetReplyTo(msg.smtpMsg.replyTo)
|
||||
}
|
||||
customMsg.SetSubject(msg.smtpMsg.subject)
|
||||
customMsg.AddTo(r.email)
|
||||
cText := msg.smtpMsg.text
|
||||
cHtml := msg.smtpMsg.html
|
||||
for v, value := range r.vars {
|
||||
placeHolder := fmt.Sprintf("%%recipient.%s%%", v)
|
||||
cText = strings.ReplaceAll(cText, placeHolder, value)
|
||||
cHtml = strings.ReplaceAll(cHtml, placeHolder, value)
|
||||
}
|
||||
customMsg.SetBody(mail.TextHTML, cHtml)
|
||||
customMsg.AddAlternative(mail.TextPlain, cText)
|
||||
e := customMsg.Error
|
||||
if e == nil {
|
||||
e = customMsg.Send(client)
|
||||
}
|
||||
if e == nil {
|
||||
emailSent = true
|
||||
} else {
|
||||
log.Error("Unable to send email to %s: %v", r.email, e)
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if !emailSent {
|
||||
// only send an error if no email could be sent (to avoid retry of successfully sent emails)
|
||||
return err
|
||||
}
|
||||
} else if m.mailGun != nil {
|
||||
_, _, err := m.mailGun.Send(msg.mgMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Add table
Reference in a new issue