2026-03-03 11:27:07 -06:00
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 ) )
}
2026-03-03 17:59:35 -06:00
func ( app * App ) resolveBaseURL ( ) string {
baseURL := app . baseURL
if baseURL == "" {
app . db . QueryRow ( ` SELECT value FROM config WHERE key = 'base_url' ` ) . Scan ( & baseURL )
}
return strings . TrimRight ( baseURL , "/" )
}
func ( app * App ) eventName ( ) string {
event , _ := app . getEvent ( )
if event != nil && event . Name != "" {
return event . Name
}
return "the event"
}
2026-03-03 11:27:07 -06:00
// 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 ( )
2026-03-03 17:59:35 -06:00
eventName := app . eventName ( )
2026-03-03 19:55:35 -06:00
link := fmt . Sprintf ( "%s/v/%s" , app . resolveBaseURL ( ) , * a . VolunteerToken )
2026-03-03 11:27:07 -06:00
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 )
}
2026-03-03 17:59:35 -06:00
func ( app * App ) sendConfirmationEmail ( to , name , confirmToken string ) error {
cfg := app . loadSMTPConfig ( )
eventName := app . eventName ( )
2026-03-03 19:55:35 -06:00
link := fmt . Sprintf ( "%s/confirm/%s" , app . resolveBaseURL ( ) , confirmToken )
2026-03-03 17:59:35 -06:00
subject := fmt . Sprintf ( "Please confirm your email for %s" , eventName )
body := fmt . Sprintf (
"Hi %s,\n\nThank you for signing up to volunteer at %s!\n\nPlease confirm your email address by visiting:\n%s\n\nIf you did not sign up, you can safely ignore this email.\n" ,
name , eventName , link ,
)
return sendEmail ( cfg , to , subject , body )
}
func ( app * App ) sendShiftSignupEmail ( to , name , kioskLink string ) error {
cfg := app . loadSMTPConfig ( )
eventName := app . eventName ( )
subject := fmt . Sprintf ( "Shift signups are open for %s!" , eventName )
body := fmt . Sprintf (
"Hi %s,\n\nShift signups are now open for %s!\n\nUse this link to sign up for available shifts:\n%s\n\nSee you there!\n" ,
name , eventName , kioskLink ,
)
return sendEmail ( cfg , to , subject , body )
}