The PHP app, Apache, and .htaccess are replaced by a single Go binary (standard library only). Routing, the JSON response, the landing page, the shared email design, and all four parsers are ported with the same output; JSON object keys are kept in sender order via an ordered decoder. - mail.go: SMTP delivery with quoted-printable body and RFC 2047 subject - main.go: routing, client IP from the proxy, /healthz, -check flag - render.go: email shell, escaping, table helpers, generic renderer - parser_*.go: github, grafana, json, wxinteract - main_test.go: handler, response shape, IP forwarding, every parser - Dockerfile: multi-stage build to distroless static, non-root, HEALTHCHECK - deploy/webhooks.dixon.cx.service: systemd unit for the container - README and PARSER_PROMPT updated for Go; IMPLEMENTATION_SUMMARY removed
68 lines
1.6 KiB
Go
68 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"mime/quotedprintable"
|
|
"net/smtp"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
smtpAddr = envOr("SMTP_ADDR", "127.0.0.1:25")
|
|
mailFrom = envOr("MAIL_FROM", "no-reply@dixon.cx")
|
|
)
|
|
|
|
// 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, _ := os.Hostname()
|
|
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
|
|
}
|