working on auth.

mailer, basic setup with html template and a dev treansport
This commit is contained in:
2024-11-15 21:42:15 +05:30
parent b0db98452a
commit 26a00c9f7c
45 changed files with 923 additions and 252 deletions

72
mailer/mailer.go Normal file
View File

@@ -0,0 +1,72 @@
package mailer
import (
"errors"
"net/mail"
"gitserver.in/patialtech/rano/config"
)
type (
transporter interface {
send(*message) error
}
Recipients struct {
To []mail.Address `json:"to"`
Cc []mail.Address `json:"cc"`
Bcc []mail.Address `json:"bcc"`
}
message struct {
From string `json:"from" validate:"required"`
Recipients Recipients `json:"recipients" validate:"required"`
Subject string `json:"subject" validate:"required"`
HtmlBody string `json:"htmlBody" validate:"required"`
ReplyTo *mail.Address `json:"replyTo"`
}
Template interface {
Subject() string
HtmlBody() (string, error)
}
)
func Send(to []mail.Address, tpl Template) error {
return send(Recipients{To: to}, tpl)
}
func SendCC(subject string, to, cc []mail.Address, tpl Template) error {
return send(Recipients{To: to, Cc: cc}, tpl)
}
func send(recipients Recipients, tpl Template) error {
if tpl == nil {
return errors.New("mailer: email template is nil")
}
if len(recipients.To) == 0 {
return errors.New("mailer: no recipient found")
}
// TODO remove recepient with bounce hiostory
body, err := tpl.HtmlBody()
if err != nil {
return err
}
// get ENV based transporter and send mail
return getTransporter().send(&message{
From: config.Read().MailerFrom,
Recipients: recipients,
Subject: tpl.Subject(),
HtmlBody: body,
})
}
func getTransporter() transporter {
switch config.AppEnv {
default:
return transportDev{}
}
}

120
mailer/message/_layout.html Normal file
View File

@@ -0,0 +1,120 @@
{{define "layout"}}
<html lang="en">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{.Title}}</title>
</head>
<body
style="
background-color: #f2f2f2;
margin: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 16px;
"
>
<table cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff" align="center" style="width: 100%">
<tbody>
<tr style="border-collapse: collapse">
<td align="center" bgcolor="#f2f2f2" style="border-collapse: collapse; padding: 15px">
<table
cellpadding="0"
cellspacing="0"
border="0"
bgcolor="#ffffff"
style="
width: 640px;
border: 2px solid #77808a;
background-color: #ffffff;
text-align: left;
padding: 0;
margin: 0;
"
>
<tbody>
<tr style="border-collapse: collapse">
<td style="border-collapse: collapse; padding: 15px">
<table>
<tbody>
<tr style="border-collapse: collapse">
<td style="border-collapse: collapse; padding: 0; vertical-align: top; width: 200px">
<img
src="{{.WebAssetsURL}}/mailer-logo.png"
alt="logo"
title="{{.Title}}"
style="outline: none; text-decoration: none; display: block; max-width: 175px"
/>
</td>
<td
style="
border-collapse: collapse;
padding: 0;
vertical-align: top;
text-align: right;
width: 100%;
"
>
&nbsp;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr class="m_-6736051552126560803borderRow" style="border-collapse: collapse">
<td
style="
border-collapse: collapse;
padding: 0;
background-color: #e2e2e2;
height: 1px;
line-height: 0;
"
></td>
</tr>
<tr style="border-collapse: collapse">
<td style="border-collapse: collapse; padding: 15px; overflow-wrap: anywhere">
<div style="font-size: 1em; line-height: 1.4em">
{{ template "content" .}}
<p>Thank You!</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr style="border-collapse: collapse">
<td align="center" bgcolor="#f2f2f2" style="border-collapse: collapse; padding: 15px">
<table
width="630"
cellpadding="5px"
cellspacing="0"
border="0"
style="width: 640px; font-size: 0.75em; color: #aaaaaa; text-align: left"
>
<tbody>
<tr style="border-collapse: collapse">
<td style="border-collapse: collapse; padding: 15px; padding-top: 0">
<p style="color: #333; font-size: 0.875em; line-height: 1.4em; margin: 0 0 0.75em">
<a href="{{.websiteURL}}" target="_blank" rel="noopener noreferrer">{{.websiteDomain}}</a>
</p>
{{if not .AllowReply}}
<p>
<small>
<strong>** This is a system generated email, please do not reply to it **</strong>
</small>
</p>
{{end}}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
{{end}}

48
mailer/message/render.go Normal file
View File

@@ -0,0 +1,48 @@
package message
import (
"bytes"
_ "embed"
"html/template"
"gitserver.in/patialtech/rano/config"
"gitserver.in/patialtech/rano/mailer"
"gitserver.in/patialtech/rano/util/structs"
)
//go:embed _layout.html
var layout string
type TplData struct {
WebAssetsURL string
mailer.Template
}
// render data in give HTML layout and content templates
func render(layout string, content string, data mailer.Template) (string, error) {
// layout
tpl, err := template.New("layout").Parse(layout)
if err != nil {
return "", err
}
// content
_, err = tpl.New("content").Parse(content)
if err != nil {
return "", err
}
// excute layout + content temaplte and render data
buf := new(bytes.Buffer)
d := structs.Map(data)
d["Title"] = "My App"
d["WebAssetsURL"] = config.Read().WebURL
d["AllowReply"] = false
err = tpl.ExecuteTemplate(buf, "layout", d)
if err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -0,0 +1,31 @@
package message
import (
"strings"
"testing"
)
type testmail struct {
Message string
}
func (t testmail) Subject() string {
return "Test Test"
}
func (t testmail) HtmlBody() (string, error) {
content := `<p>{{.Message}}</p>`
return render(layout, content, t)
}
func TestRender(t *testing.T) {
tpl := testmail{
Message: "some mesage",
}
if b, err := tpl.HtmlBody(); err != nil {
t.Error(err)
} else if !strings.Contains(b, tpl.Message) {
t.Error("supposed to contain:", tpl.Message)
}
}

16
mailer/message/welcome.go Normal file
View File

@@ -0,0 +1,16 @@
package message
type Welcome struct {
Name string
}
func (e *Welcome) Subject() string {
return "Welcome"
}
func (e *Welcome) HtmlBody() (string, error) {
content := `
<p>Welcome {{.Name}}</p>
`
return render(layout, content, e)
}

23
mailer/transport_dev.go Normal file
View File

@@ -0,0 +1,23 @@
package mailer
import (
"os"
"path/filepath"
"time"
"gitserver.in/patialtech/rano/util/open"
)
type transportDev struct{}
func (transportDev) send(msg *message) error {
dir := os.TempDir()
id := time.Now().Format("20060102T150405999")
file := filepath.Join(dir, id+".html")
if err := os.WriteFile(file, []byte(msg.HtmlBody), 0440); err != nil {
return err
}
return open.WithDefaultApp(file)
}