2022-08-10 05:10:47 +00:00
|
|
|
// Package mail provides e-mail sending faculties.
|
|
|
|
package mail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"strings"
|
2024-05-20 01:33:28 +00:00
|
|
|
"time"
|
2022-08-10 05:10:47 +00:00
|
|
|
|
2022-08-26 04:21:46 +00:00
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
2024-05-20 01:33:28 +00:00
|
|
|
"code.nonshy.com/nonshy/website/pkg/encryption"
|
2022-08-26 04:21:46 +00:00
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
2024-05-20 01:33:28 +00:00
|
|
|
"code.nonshy.com/nonshy/website/pkg/redis"
|
2022-08-10 05:10:47 +00:00
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
|
|
"gopkg.in/gomail.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Message configuration.
|
|
|
|
type Message struct {
|
|
|
|
To string
|
|
|
|
ReplyTo string
|
|
|
|
Subject string
|
|
|
|
Template string // path relative to the templates dir, e.g. "email/verify_email.html"
|
|
|
|
Data map[string]interface{}
|
|
|
|
}
|
|
|
|
|
2024-05-20 01:33:28 +00:00
|
|
|
// LockSending emails to the same address within 24 hours, e.g.: on the signup form to reduce chance for spam abuse.
|
|
|
|
//
|
|
|
|
// Call this before calling Send() if you want to throttle the sending. This function will put a key in Redis on
|
|
|
|
// the first call and return nil; on subsequent calls, if the key still remains, it will return an error.
|
|
|
|
func LockSending(namespace, email string, expires time.Duration) error {
|
|
|
|
var key = fmt.Sprintf("mail/lock-sending/%s/%s", namespace, encryption.Hash([]byte(email)))
|
|
|
|
|
|
|
|
// See if we have already locked it.
|
|
|
|
if redis.Exists(key) {
|
|
|
|
return errors.New("email was in the lock-sending queue")
|
|
|
|
}
|
|
|
|
|
|
|
|
redis.Set(key, email, expires)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-10 05:10:47 +00:00
|
|
|
// Send an email.
|
|
|
|
func Send(msg Message) error {
|
|
|
|
conf := config.Current.Mail
|
|
|
|
|
|
|
|
// Verify configuration.
|
|
|
|
if !conf.Enabled {
|
|
|
|
return errors.New(
|
|
|
|
"Email sending is not configured for this app. Please contact the website administrator about this error.",
|
|
|
|
)
|
|
|
|
} else if conf.Host == "" || conf.Port == 0 || conf.From == "" {
|
|
|
|
return errors.New(
|
|
|
|
"Email settings are misconfigured for this app. Please contact the website administrator about this error.",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get and render the template to HTML.
|
|
|
|
var html bytes.Buffer
|
|
|
|
tmpl, err := template.New(msg.Template).ParseFiles(config.TemplatePath + "/" + msg.Template)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the template.
|
|
|
|
err = tmpl.ExecuteTemplate(&html, "content", msg)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Mail template execute error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Condense the HTML down into the plaintext version.
|
|
|
|
rawLines := strings.Split(
|
|
|
|
bluemonday.StrictPolicy().Sanitize(html.String()),
|
|
|
|
"\n",
|
|
|
|
)
|
|
|
|
var lines []string
|
|
|
|
for _, line := range rawLines {
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
if len(line) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
plaintext := strings.Join(lines, "\n\n")
|
|
|
|
|
|
|
|
// Prepare the e-mail!
|
|
|
|
m := gomail.NewMessage()
|
|
|
|
m.SetHeader("From", fmt.Sprintf("%s <%s>", config.Title, conf.From))
|
|
|
|
m.SetHeader("To", msg.To)
|
|
|
|
if msg.ReplyTo != "" {
|
|
|
|
m.SetHeader("Reply-To", msg.ReplyTo)
|
|
|
|
}
|
|
|
|
m.SetHeader("Subject", msg.Subject)
|
|
|
|
m.SetBody("text/plain", plaintext)
|
|
|
|
m.AddAlternative("text/html", html.String())
|
|
|
|
|
2022-08-21 21:05:08 +00:00
|
|
|
// Deliver asynchronously.
|
2022-08-10 05:10:47 +00:00
|
|
|
log.Info("mail.Send: %s (%s) to %s", msg.Subject, msg.Template, msg.To)
|
2022-08-21 21:05:08 +00:00
|
|
|
d := gomail.NewDialer(conf.Host, conf.Port, conf.Username, conf.Password)
|
|
|
|
go func() {
|
|
|
|
if err := d.DialAndSend(m); err != nil {
|
|
|
|
log.Error("mail.Send: %s", err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2022-08-10 05:10:47 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|