This commit is contained in:
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
const timeout = 15 * time.Second
|
||||
|
||||
// Config is read once at boot. When Host is empty the service still accepts and
|
||||
// stores submissions; it just does not email them.
|
||||
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port string
|
||||
@@ -41,19 +41,19 @@ func (c Config) Enabled() bool {
|
||||
return c.Host != "" && c.From != "" && c.To != ""
|
||||
}
|
||||
|
||||
// Port 465 uses implicit TLS; any other port starts plain and upgrades with
|
||||
// STARTTLS when the server advertises it. Dial and connection deadlines keep an
|
||||
// unreachable host from hanging the caller until the OS TCP timeout.
|
||||
// Send delivers a plain-text message. replyTo, when set, becomes the Reply-To
|
||||
// header so hitting reply in a mail client answers the person who filled in the
|
||||
// form rather than the service's own sending address. The envelope sender stays
|
||||
// as From, so a submitted address can never affect SPF or DMARC alignment.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func (c Config) Send(subject, body, replyTo string) error {
|
||||
return c.sendTo(c.To, subject, body, replyTo)
|
||||
}
|
||||
|
||||
// sendTo delivers to an explicit recipient. Contact enquiries go to the support
|
||||
// inbox (c.To); verification links go to the person signing up.
|
||||
|
||||
|
||||
func (c Config) sendTo(to, subject, body, replyTo string) error {
|
||||
if !c.Enabled() {
|
||||
return fmt.Errorf("smtp: not configured")
|
||||
@@ -127,9 +127,9 @@ func recipients(to string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// message builds the MIME body. The subject is encoded rather than interpolated
|
||||
// raw, and headers are stripped of CR/LF so submitted content cannot inject
|
||||
// extra headers.
|
||||
|
||||
|
||||
|
||||
func message(from, to, subject, body, replyTo string) []byte {
|
||||
var b strings.Builder
|
||||
b.WriteString("From: " + sanitizeHeader(from) + "\r\n")
|
||||
@@ -137,10 +137,10 @@ func message(from, to, subject, body, replyTo string) []byte {
|
||||
if replyTo != "" {
|
||||
b.WriteString("Reply-To: " + sanitizeHeader(replyTo) + "\r\n")
|
||||
}
|
||||
// Date and Message-ID are RFC 5322 essentials. Without them many servers
|
||||
// accept the message at SMTP time and then silently junk or drop it, and
|
||||
// SpamAssassin scores MISSING_DATE and MISSING_MID heavily — the message
|
||||
// "sends" but never lands in the inbox.
|
||||
|
||||
|
||||
|
||||
|
||||
b.WriteString("Date: " + time.Now().Format(time.RFC1123Z) + "\r\n")
|
||||
b.WriteString("Message-ID: " + messageID(from) + "\r\n")
|
||||
b.WriteString("Subject: " + mime.QEncoding.Encode("utf-8", sanitizeHeader(subject)) + "\r\n")
|
||||
@@ -151,9 +151,9 @@ func message(from, to, subject, body, replyTo string) []byte {
|
||||
return []byte(b.String())
|
||||
}
|
||||
|
||||
// messageID builds a unique <id@domain>, taking the domain from the From
|
||||
// address so the identifier matches the sending domain. Falls back to the host
|
||||
// name when From has no domain part.
|
||||
|
||||
|
||||
|
||||
func messageID(from string) string {
|
||||
domain := "vantage.local"
|
||||
if at := strings.LastIndex(from, "@"); at >= 0 && at < len(from)-1 {
|
||||
@@ -170,9 +170,9 @@ func sanitizeHeader(v string) string {
|
||||
return strings.NewReplacer("\r", " ", "\n", " ").Replace(v)
|
||||
}
|
||||
|
||||
// SendVerification emails the one-time link that completes a signup. The
|
||||
// address is the person signing up, not the support inbox, so To is overridden
|
||||
// for this one message.
|
||||
|
||||
|
||||
|
||||
func (c Config) SendVerification(to, orgName, link string, ttl time.Duration) error {
|
||||
body := fmt.Sprintf(`Confirm your email to finish creating %s on Vantage.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user