Improve mail service
This commit is contained in:
		@ -5,10 +5,8 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/tsawler/toolbox"
 | 
			
		||||
	mail "github.com/xhit/go-simple-mail/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MailData struct {
 | 
			
		||||
@ -35,7 +33,13 @@ func (app *Config) SendMail(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = app.SendMsg(mailData)
 | 
			
		||||
	msg := Message{
 | 
			
		||||
		From:    mailData.From,
 | 
			
		||||
		To:      mailData.To,
 | 
			
		||||
		Subject: mailData.Subject,
 | 
			
		||||
		Data:    mailData.Content,
 | 
			
		||||
	}
 | 
			
		||||
	err = app.Mailer.SendSMTPMessage(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		app.Tools.ErrorJSON(w, err)
 | 
			
		||||
@ -48,37 +52,3 @@ func (app *Config) SendMail(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 | 
			
		||||
	app.Tools.WriteJSON(w, http.StatusAccepted, payload)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *Config) SendMsg(m MailData) error {
 | 
			
		||||
	server := mail.NewSMTPClient()
 | 
			
		||||
	server.Host = app.Host
 | 
			
		||||
	server.Port = app.Port
 | 
			
		||||
 | 
			
		||||
	server.KeepAlive = false
 | 
			
		||||
	server.ConnectTimeout = 10 * time.Second
 | 
			
		||||
	server.SendTimeout = 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	smtpClient, err := server.Connect()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Maybe add a template
 | 
			
		||||
 | 
			
		||||
	email := mail.NewMSG()
 | 
			
		||||
	email.SetFrom(m.From).
 | 
			
		||||
		AddTo(m.To).
 | 
			
		||||
		SetSubject(m.Subject).
 | 
			
		||||
		SetBody(mail.TextHTML, m.Content)
 | 
			
		||||
 | 
			
		||||
	err = email.Send(smtpClient)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("Email sent to %s", m.To)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										168
									
								
								mail-service/cmd/api/mailer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								mail-service/cmd/api/mailer.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,168 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"embed"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/vanng822/go-premailer/premailer"
 | 
			
		||||
	mail "github.com/xhit/go-simple-mail/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//go:embed templates
 | 
			
		||||
var emailTemplateFS embed.FS
 | 
			
		||||
 | 
			
		||||
type Mail struct {
 | 
			
		||||
	Domain     string
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       int
 | 
			
		||||
	Username   string
 | 
			
		||||
	Password   string
 | 
			
		||||
	Encryption string
 | 
			
		||||
	FromAddr   string
 | 
			
		||||
	FromName   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	From        string
 | 
			
		||||
	FromName    string
 | 
			
		||||
	To          string
 | 
			
		||||
	Subject     string
 | 
			
		||||
	Attachments []string
 | 
			
		||||
	Data        any
 | 
			
		||||
	DataMap     map[string]any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mail) SendSMTPMessage(msg Message) error {
 | 
			
		||||
	if msg.From == "" {
 | 
			
		||||
		msg.From = m.FromAddr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if msg.FromName == "" {
 | 
			
		||||
		msg.FromName = m.FromName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := map[string]any{
 | 
			
		||||
		"message": msg.Data,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg.DataMap = data
 | 
			
		||||
 | 
			
		||||
	formattedMessage, err := m.buildHTMLMessage(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	plainMessage, err := m.buildPlainTextMessage(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	server := mail.NewSMTPClient()
 | 
			
		||||
	server.Host = m.Host
 | 
			
		||||
	server.Port = m.Port
 | 
			
		||||
	server.Username = m.Username
 | 
			
		||||
	server.Password = m.Password
 | 
			
		||||
	server.Encryption = m.getEncryption(m.Encryption)
 | 
			
		||||
	server.KeepAlive = false
 | 
			
		||||
	server.ConnectTimeout = 10 * time.Second
 | 
			
		||||
	server.SendTimeout = 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	log.Println(server.Host, server.Port)
 | 
			
		||||
 | 
			
		||||
	smtpClient, err := server.Connect()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	email := mail.NewMSG()
 | 
			
		||||
	email.SetFrom(msg.From).AddTo(msg.To).SetSubject(msg.Subject)
 | 
			
		||||
 | 
			
		||||
	email.SetBody(mail.TextPlain, plainMessage)
 | 
			
		||||
	email.AddAlternative(mail.TextHTML, formattedMessage)
 | 
			
		||||
 | 
			
		||||
	if len(msg.Attachments) > 0 {
 | 
			
		||||
		for _, x := range msg.Attachments {
 | 
			
		||||
			email.AddAttachment(x)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = email.Send(smtpClient)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mail) buildPlainTextMessage(msg Message) (string, error) {
 | 
			
		||||
	templateToRender := "templates/mail.plain.gohtml"
 | 
			
		||||
 | 
			
		||||
	t, err := template.New("email-plain").ParseFS(emailTemplateFS, templateToRender)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tpl bytes.Buffer
 | 
			
		||||
	if err = t.ExecuteTemplate(&tpl, "body", msg.DataMap); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	formattedMessage := tpl.String()
 | 
			
		||||
 | 
			
		||||
	return formattedMessage, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mail) buildHTMLMessage(msg Message) (string, error) {
 | 
			
		||||
	templateToRender := "templates/mail.html.gohtml"
 | 
			
		||||
 | 
			
		||||
	t, err := template.New("email-html").ParseFS(emailTemplateFS, templateToRender)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tpl bytes.Buffer
 | 
			
		||||
	if err = t.ExecuteTemplate(&tpl, "body", msg.DataMap); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	formattedMessage := tpl.String()
 | 
			
		||||
	formattedMessage, err = m.inlineCSS(formattedMessage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return formattedMessage, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mail) inlineCSS(s string) (string, error) {
 | 
			
		||||
	options := premailer.Options{
 | 
			
		||||
		RemoveClasses:     false,
 | 
			
		||||
		CssToAttributes:   false,
 | 
			
		||||
		KeepBangImportant: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prem, err := premailer.NewPremailerFromString(s, &options)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	html, err := prem.Transform()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return html, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mail) getEncryption(s string) mail.Encryption {
 | 
			
		||||
	switch s {
 | 
			
		||||
	case "tls":
 | 
			
		||||
		return mail.EncryptionSTARTTLS
 | 
			
		||||
	case "ssl":
 | 
			
		||||
		return mail.EncryptionSSLTLS
 | 
			
		||||
	case "none":
 | 
			
		||||
		return mail.EncryptionNone
 | 
			
		||||
	default:
 | 
			
		||||
		return mail.EncryptionNone
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -4,6 +4,8 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/tsawler/toolbox"
 | 
			
		||||
)
 | 
			
		||||
@ -17,12 +19,12 @@ type Config struct {
 | 
			
		||||
	Password   string
 | 
			
		||||
	Encryption string
 | 
			
		||||
	Tools      toolbox.Tools
 | 
			
		||||
	Mailer     Mail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	app := Config{
 | 
			
		||||
		Host: "mailhog",
 | 
			
		||||
		Port: 1025,
 | 
			
		||||
		Mailer: createMail(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("Starting mail service on port %s\n", webPort)
 | 
			
		||||
@ -38,3 +40,17 @@ func main() {
 | 
			
		||||
		log.Panic(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createMail() Mail {
 | 
			
		||||
	port, _ := strconv.Atoi(os.Getenv("MAIL_PORT"))
 | 
			
		||||
	return Mail{
 | 
			
		||||
		Domain:     os.Getenv("MAIL_DOMAIN"),
 | 
			
		||||
		Host:       os.Getenv("MAIL_HOST"),
 | 
			
		||||
		Port:       port,
 | 
			
		||||
		Username:   os.Getenv("MAIL_USERNAME"),
 | 
			
		||||
		Password:   os.Getenv("MAIL_PASSWORD"),
 | 
			
		||||
		Encryption: os.Getenv("MAIL_ENCRYPTION"),
 | 
			
		||||
		FromName:   os.Getenv("FROM_NAME"),
 | 
			
		||||
		FromAddr:   os.Getenv("FROM_ADDR"),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								mail-service/cmd/api/templates/mail.html.gohtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								mail-service/cmd/api/templates/mail.html.gohtml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
{{ define "body" }}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta name="viewport" content="width=device-width">
 | 
			
		||||
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
			
		||||
        <title></title>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <p>{{.message}}</p>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
{{ end }}
 | 
			
		||||
							
								
								
									
										3
									
								
								mail-service/cmd/api/templates/mail.plain.gohtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								mail-service/cmd/api/templates/mail.plain.gohtml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
{{ define "body" }}
 | 
			
		||||
{{.message}}
 | 
			
		||||
{{ end }}
 | 
			
		||||
		Reference in New Issue
	
	Block a user