working on auth.
mailer, basic setup with html template and a dev treansport
This commit is contained in:
72
mailer/mailer.go
Normal file
72
mailer/mailer.go
Normal 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
120
mailer/message/_layout.html
Normal 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%;
|
||||
"
|
||||
>
|
||||
|
||||
</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
48
mailer/message/render.go
Normal 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
|
||||
}
|
31
mailer/message/render_test.go
Normal file
31
mailer/message/render_test.go
Normal 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
16
mailer/message/welcome.go
Normal 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
23
mailer/transport_dev.go
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user