Initial work on email templates
At this point I got a nice-looking formatted message in my mail client.
This commit is contained in:
parent
087f29d491
commit
2c880568dd
5 changed files with 298 additions and 12 deletions
|
|
@ -2,6 +2,7 @@ package comms
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -11,12 +12,46 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type AttachmentRequest struct {
|
||||
func SendEmailReportConfirmation(to string, report_id string) error {
|
||||
content := contentEmailReportConfirmation{
|
||||
URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png",
|
||||
URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id),
|
||||
URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id),
|
||||
URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/subscribe-confirmation", report_id),
|
||||
}
|
||||
text, html, err := renderEmailTemplates(reportConfirmationT, content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err)
|
||||
}
|
||||
return sendEmail(emailRequest{
|
||||
From: config.ForwardEmailReportAddress,
|
||||
HTML: html,
|
||||
Subject: fmt.Sprintf("Mosquito Report %s Submission", report_id),
|
||||
Text: text,
|
||||
To: to,
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
reportConfirmationT = buildTemplate("report-subscription-confirmation")
|
||||
)
|
||||
|
||||
//go:embed template/*
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
type attachmentRequest struct {
|
||||
Filename string `json:"filename"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type EmailRequest struct {
|
||||
type contentEmailReportConfirmation struct {
|
||||
URLLogo string
|
||||
URLReportStatus string
|
||||
URLReportUnsubscribe string
|
||||
URLViewInBrowser string
|
||||
}
|
||||
|
||||
type emailRequest struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
CC []string `json:"cc,omitempty"`
|
||||
|
|
@ -24,18 +59,18 @@ type EmailRequest struct {
|
|||
Subject string `json:"subject"`
|
||||
Text string `json:"text"`
|
||||
HTML string `json:"html,omitempty"`
|
||||
Attachments []AttachmentRequest `json:"attachments,omitempty"`
|
||||
Attachments []attachmentRequest `json:"attachments,omitempty"`
|
||||
Sender string `json:"sender"`
|
||||
ReplyTo string `json:"replyTo,omitempty"`
|
||||
InReplyTo string `json:"inReplyTo,omitempty"`
|
||||
References []string `json:"references,omitempty"`
|
||||
}
|
||||
|
||||
type EmailResponse struct {
|
||||
type emailResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func SendEmail(email EmailRequest) error {
|
||||
func sendEmail(email emailRequest) error {
|
||||
url := "https://api.forwardemail.net/v1/emails"
|
||||
|
||||
payload, err := json.Marshal(email)
|
||||
|
|
|
|||
146
comms/template.go
Normal file
146
comms/template.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package comms
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
templatehtml "html/template"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
templatetxt "text/template"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type builtTemplate struct {
|
||||
name string
|
||||
templateHTML *templatehtml.Template
|
||||
templateTXT *templatetxt.Template
|
||||
}
|
||||
|
||||
func (bt *builtTemplate) executeTemplateHTML(w io.Writer, content any) error {
|
||||
if bt.templateHTML == nil {
|
||||
file := templateFileHTML(bt.name)
|
||||
templ, err := parseFromDiskHTML(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse template file: %w", err)
|
||||
}
|
||||
if templ == nil {
|
||||
w.Write([]byte("Failed to read from disk: "))
|
||||
return errors.New("Template parsing failed")
|
||||
}
|
||||
//log.Debug().Str("name", templ.Name()).Msg("Parsed template")
|
||||
return templ.ExecuteTemplate(w, bt.name+".html", content)
|
||||
} else {
|
||||
return bt.templateHTML.ExecuteTemplate(w, bt.name+".html", content)
|
||||
}
|
||||
}
|
||||
func (bt *builtTemplate) executeTemplateTXT(w io.Writer, content any) error {
|
||||
if bt.templateTXT == nil {
|
||||
file := templateFileTXT(bt.name)
|
||||
templ, err := parseFromDiskTXT(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse template file: %w", err)
|
||||
}
|
||||
if templ == nil {
|
||||
w.Write([]byte("Failed to read from disk: "))
|
||||
return errors.New("Template parsing failed")
|
||||
}
|
||||
//log.Debug().Str("name", templ.Name()).Msg("Parsed template")
|
||||
return templ.ExecuteTemplate(w, bt.name+".txt", content)
|
||||
} else {
|
||||
return bt.templateTXT.ExecuteTemplate(w, bt.name+".txt", content)
|
||||
}
|
||||
}
|
||||
func templateFileHTML(name string) string {
|
||||
return fmt.Sprintf("comms/template/%s.html", name)
|
||||
}
|
||||
func templateFileTXT(name string) string {
|
||||
return fmt.Sprintf("comms/template/%s.txt", name)
|
||||
}
|
||||
|
||||
func buildTemplate(name string) *builtTemplate {
|
||||
files_on_disk := true
|
||||
file_html := templateFileHTML(name)
|
||||
file_txt := templateFileTXT(name)
|
||||
for _, f := range []string{file_html, file_txt} {
|
||||
_, err := os.Stat(f)
|
||||
if err != nil {
|
||||
files_on_disk = false
|
||||
if !config.IsProductionEnvironment() {
|
||||
log.Warn().Str("file", f).Msg("email template file is not on disk")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
var result builtTemplate
|
||||
if files_on_disk {
|
||||
result = builtTemplate{
|
||||
name: name,
|
||||
templateHTML: nil,
|
||||
templateTXT: nil,
|
||||
}
|
||||
} else {
|
||||
result = builtTemplate{
|
||||
name: name,
|
||||
templateHTML: parseEmbeddedHTML(embeddedFiles, "comms", file_html),
|
||||
templateTXT: parseEmbeddedTXT(embeddedFiles, "comms", file_txt),
|
||||
}
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *templatehtml.Template {
|
||||
// Remap the file names to embedded paths
|
||||
embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)}
|
||||
name := path.Base(embeddedFilePaths[0])
|
||||
log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template")
|
||||
return templatehtml.Must(
|
||||
templatehtml.New(name).ParseFS(embeddedFiles, embeddedFilePaths...))
|
||||
}
|
||||
func parseEmbeddedTXT(embeddedFiles embed.FS, subdir string, file string) *templatetxt.Template {
|
||||
// Remap the file names to embedded paths
|
||||
embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)}
|
||||
name := path.Base(embeddedFilePaths[0])
|
||||
log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template")
|
||||
return templatetxt.Must(
|
||||
templatetxt.New(name).ParseFS(embeddedFiles, embeddedFilePaths...))
|
||||
}
|
||||
|
||||
func parseFromDiskHTML(file string) (*templatehtml.Template, error) {
|
||||
name := path.Base(file)
|
||||
//log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk")
|
||||
templ, err := templatehtml.New(name).ParseFiles(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s: %w", file, err)
|
||||
}
|
||||
return templ, nil
|
||||
}
|
||||
|
||||
func parseFromDiskTXT(file string) (*templatetxt.Template, error) {
|
||||
name := path.Base(file)
|
||||
//log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk")
|
||||
templ, err := templatetxt.New(name).ParseFiles(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s: %w", file, err)
|
||||
}
|
||||
return templ, nil
|
||||
}
|
||||
|
||||
func renderEmailTemplates(t *builtTemplate, content interface{}) (text string, html string, err error) {
|
||||
buf_txt := &bytes.Buffer{}
|
||||
err = t.executeTemplateTXT(buf_txt, content)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Failed to render TXT template: %w", err)
|
||||
}
|
||||
buf_html := &bytes.Buffer{}
|
||||
err = t.executeTemplateHTML(buf_html, content)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Failed to render HTML template: %w", err)
|
||||
}
|
||||
return buf_txt.String(), buf_html.String(), nil
|
||||
}
|
||||
99
comms/template/report-subscription-confirmation.html
Normal file
99
comms/template/report-subscription-confirmation.html
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Thank You for Your Mosquito Report</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #333333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.view-browser {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #777777;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.view-browser a {
|
||||
color: #555555;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.logo {
|
||||
max-width: 150px;
|
||||
height: auto;
|
||||
}
|
||||
.content {
|
||||
background-color: #f9f9f9;
|
||||
padding: 30px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
background-color: #0066cc;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
padding: 12px 25px;
|
||||
border-radius: 4px;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #777777;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.footer a {
|
||||
color: #777777;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="view-browser">
|
||||
Email not displaying correctly? <a href="{{.URLViewInBrowser}}">View it in your browser</a>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<!-- Logo Placeholder -->
|
||||
<img src="{{.URLLogo}}" alt="Report Mosquitoes Online Logo" class="logo">
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h1>Thank You for Your Report</h1>
|
||||
|
||||
<p>We've received your mosquito report. Thanks! We appreciate you taking the time to submit it.</p>
|
||||
|
||||
<p>You can check the current status of your report at any time by clicking the button below:</p>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="{{.URLReportStatus}}" class="button">View Report Status</a>
|
||||
</div>
|
||||
|
||||
<p>We'll send you additional updates as work is scheduled and completed.</p>
|
||||
|
||||
<p>If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>This email was sent to you because you requested updates on your mosquito nuisance report.</p>
|
||||
<p>If you no longer wish to receive these updates, <a href="{{.URLReportUnsubscribe}}">click here to unsubscribe</a>.</p>
|
||||
<p>© 2026 Gleipnir Technology. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
9
comms/template/report-subscription-confirmation.txt
Normal file
9
comms/template/report-subscription-confirmation.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
We've received your mosquito report. Thanks! We appreciate you taking the time to submit it.
|
||||
|
||||
You can check the current status of your report at any time at {{.URLReportStatus}}
|
||||
|
||||
We'll send you additional updates as work is scheduled and completed.
|
||||
|
||||
If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email.
|
||||
|
||||
If you no longer wish to receive these updates, navigate your browser to {{.URLReportUnsubscribe}} to unsubscribe.
|
||||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
|
|
@ -182,12 +181,10 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if email != "" {
|
||||
comms.SendEmail(comms.EmailRequest{
|
||||
From: config.ForwardEmailReportAddress,
|
||||
To: email,
|
||||
Subject: "test email",
|
||||
Text: "This is just testing that I can send email",
|
||||
})
|
||||
err := comms.SendEmailReportConfirmation(email, report_id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to send email")
|
||||
}
|
||||
}
|
||||
if phone != "" {
|
||||
//err := comms.SendSMS(phone, "testing 1 2 3")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue