Mail now comes from no-reply@webhook.me.uk (DKIM, SPF and DMARC are set up for it) and the Message-ID uses that domain instead of the container hostname. The landing page gets a favicon, an Open Graph image, canonical/OG/Twitter/JSON-LD tags, robots.txt that keeps crawlers off email URLs, and a sitemap. Footer links to dixon.cx.
69 lines
1.6 KiB
Go
69 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"mime/quotedprintable"
|
|
"net/smtp"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
smtpAddr = envOr("SMTP_ADDR", "127.0.0.1:25")
|
|
mailFrom = envOr("MAIL_FROM", "no-reply@webhook.me.uk")
|
|
)
|
|
|
|
// sendMail is a variable so tests can capture messages instead of sending them.
|
|
var sendMail = smtpSend
|
|
|
|
func smtpSend(to, subject, htmlBody string) error {
|
|
c, err := smtp.Dial(smtpAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer c.Close()
|
|
if err := c.Mail(mailFrom); err != nil {
|
|
return err
|
|
}
|
|
if err := c.Rcpt(to); err != nil {
|
|
return err
|
|
}
|
|
w, err := c.Data()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.Write(buildMessage(to, subject, htmlBody)); err != nil {
|
|
return err
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
return err
|
|
}
|
|
return c.Quit()
|
|
}
|
|
|
|
func buildMessage(to, subject, htmlBody string) []byte {
|
|
host := mailFrom[strings.LastIndex(mailFrom, "@")+1:]
|
|
var b []byte
|
|
b = fmt.Appendf(b, "From: Webhook Call <%s>\r\n", mailFrom)
|
|
b = fmt.Appendf(b, "To: <%s>\r\n", to)
|
|
b = fmt.Appendf(b, "Subject: %s\r\n", mime.QEncoding.Encode("utf-8", subject))
|
|
b = fmt.Appendf(b, "Date: %s\r\n", time.Now().Format(time.RFC1123Z))
|
|
b = fmt.Appendf(b, "Message-ID: <%d.%d@%s>\r\n", time.Now().UnixNano(), os.Getpid(), host)
|
|
b = append(b, "MIME-Version: 1.0\r\n"...)
|
|
b = append(b, "Content-Type: text/html; charset=UTF-8\r\n"...)
|
|
b = append(b, "Content-Transfer-Encoding: quoted-printable\r\n\r\n"...)
|
|
qp := quotedprintable.NewWriter(sliceWriter{&b})
|
|
_, _ = qp.Write([]byte(htmlBody))
|
|
_ = qp.Close()
|
|
return b
|
|
}
|
|
|
|
type sliceWriter struct{ b *[]byte }
|
|
|
|
func (s sliceWriter) Write(p []byte) (int, error) {
|
|
*s.b = append(*s.b, p...)
|
|
return len(p), nil
|
|
}
|