141 lines
3.5 KiB
Go
141 lines
3.5 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/tls"
|
||
|
|
"fmt"
|
||
|
|
"net/smtp"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type SMTPConfig struct {
|
||
|
|
Host string
|
||
|
|
Port int
|
||
|
|
User string
|
||
|
|
Password string
|
||
|
|
From string
|
||
|
|
FromName string
|
||
|
|
}
|
||
|
|
|
||
|
|
// loadSMTPConfig reads SMTP settings from the config table, overlaying any
|
||
|
|
// values set via CLI flags (which take priority).
|
||
|
|
func (app *App) loadSMTPConfig() SMTPConfig {
|
||
|
|
get := func(key string) string {
|
||
|
|
var v string
|
||
|
|
app.db.QueryRow(`SELECT value FROM config WHERE key = ?`, key).Scan(&v)
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg := SMTPConfig{
|
||
|
|
Host: app.smtpHost,
|
||
|
|
Port: app.smtpPort,
|
||
|
|
User: app.smtpUser,
|
||
|
|
Password: app.smtpPassword,
|
||
|
|
From: app.smtpFrom,
|
||
|
|
FromName: app.smtpFromName,
|
||
|
|
}
|
||
|
|
|
||
|
|
if cfg.Host == "" {
|
||
|
|
cfg.Host = get("smtp_host")
|
||
|
|
}
|
||
|
|
if cfg.Port == 0 {
|
||
|
|
fmt.Sscanf(get("smtp_port"), "%d", &cfg.Port)
|
||
|
|
}
|
||
|
|
if cfg.User == "" {
|
||
|
|
cfg.User = get("smtp_user")
|
||
|
|
}
|
||
|
|
if cfg.Password == "" {
|
||
|
|
cfg.Password = get("smtp_password")
|
||
|
|
}
|
||
|
|
if cfg.From == "" {
|
||
|
|
cfg.From = get("smtp_from")
|
||
|
|
}
|
||
|
|
if cfg.FromName == "" {
|
||
|
|
cfg.FromName = get("smtp_from_name")
|
||
|
|
}
|
||
|
|
if cfg.Port == 0 {
|
||
|
|
cfg.Port = 587
|
||
|
|
}
|
||
|
|
return cfg
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendEmail delivers a plain-text email.
|
||
|
|
// Uses implicit TLS on port 465, STARTTLS on all other ports.
|
||
|
|
func sendEmail(cfg SMTPConfig, to, subject, body string) error {
|
||
|
|
fromHeader := cfg.From
|
||
|
|
if cfg.FromName != "" {
|
||
|
|
fromHeader = fmt.Sprintf("%s <%s>", cfg.FromName, cfg.From)
|
||
|
|
}
|
||
|
|
msg := fmt.Sprintf(
|
||
|
|
"From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||
|
|
fromHeader, to, subject, body,
|
||
|
|
)
|
||
|
|
|
||
|
|
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||
|
|
auth := smtp.PlainAuth("", cfg.User, cfg.Password, cfg.Host)
|
||
|
|
|
||
|
|
if cfg.Port == 465 {
|
||
|
|
tlsCfg := &tls.Config{ServerName: cfg.Host}
|
||
|
|
conn, err := tls.Dial("tcp", addr, tlsCfg)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("tls dial: %w", err)
|
||
|
|
}
|
||
|
|
c, err := smtp.NewClient(conn, cfg.Host)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("smtp client: %w", err)
|
||
|
|
}
|
||
|
|
defer c.Close()
|
||
|
|
if err = c.Auth(auth); err != nil {
|
||
|
|
return fmt.Errorf("smtp auth: %w", err)
|
||
|
|
}
|
||
|
|
if err = c.Mail(cfg.From); err != nil {
|
||
|
|
return fmt.Errorf("smtp mail from: %w", err)
|
||
|
|
}
|
||
|
|
if err = c.Rcpt(to); err != nil {
|
||
|
|
return fmt.Errorf("smtp rcpt: %w", err)
|
||
|
|
}
|
||
|
|
w, err := c.Data()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("smtp data: %w", err)
|
||
|
|
}
|
||
|
|
if _, err = fmt.Fprint(w, msg); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return w.Close()
|
||
|
|
}
|
||
|
|
|
||
|
|
return smtp.SendMail(addr, auth, cfg.From, []string{to}, []byte(msg))
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendTokenEmail sends a volunteer token link to the attendee's email address.
|
||
|
|
func (app *App) sendTokenEmail(a Attendee) error {
|
||
|
|
if a.Email == "" {
|
||
|
|
return fmt.Errorf("attendee has no email address")
|
||
|
|
}
|
||
|
|
if a.VolunteerToken == nil || *a.VolunteerToken == "" {
|
||
|
|
return fmt.Errorf("attendee has no volunteer token")
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg := app.loadSMTPConfig()
|
||
|
|
|
||
|
|
baseURL := app.baseURL
|
||
|
|
if baseURL == "" {
|
||
|
|
app.db.QueryRow(`SELECT value FROM config WHERE key = 'base_url'`).Scan(&baseURL)
|
||
|
|
}
|
||
|
|
baseURL = strings.TrimRight(baseURL, "/")
|
||
|
|
|
||
|
|
event, _ := app.getEvent()
|
||
|
|
eventName := "the event"
|
||
|
|
if event != nil && event.Name != "" {
|
||
|
|
eventName = event.Name
|
||
|
|
}
|
||
|
|
|
||
|
|
link := fmt.Sprintf("%s/#/v/%s", baseURL, *a.VolunteerToken)
|
||
|
|
subject := fmt.Sprintf("Your volunteer link for %s", eventName)
|
||
|
|
body := fmt.Sprintf(
|
||
|
|
"Hi %s,\n\nThank you for volunteering at %s!\n\nYour volunteer token: %s\nYour signup link: %s\n\nUse this link to sign up for available shifts in your department.\n\nSee you there!\n",
|
||
|
|
a.Name, eventName, *a.VolunteerToken, link,
|
||
|
|
)
|
||
|
|
|
||
|
|
return sendEmail(cfg, a.Email, subject, body)
|
||
|
|
}
|