// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT //go:build linux || freebsd package email import ( "bytes" "fmt" "net/smtp" "time" "sourcedock.dev/petrbalvin/nuntius/internal/config" ) // SendConfirmation mails the double opt-in link directly to the subscriber. // It uses the form's SMTP identity so replies land at the owner address, // which is set as Reply-To. func (s *FormSender) SendConfirmation(to, link string) error { auth := smtp.PlainAuth("", s.form.SMTP.User, s.form.SMTP.Password, s.form.SMTP.Host) msg := composeConfirmation(s.form.From, to, s.form.To, s.form, link) return sendMail(s.form.SMTP, auth, s.form.From, []string{to}, msg, s.form.SMTP.Timeout(), s.TLSConfig) } // composeConfirmation builds a single-part plain-text message. Transactional // confirmations stay deliberately simple: one link, no tracking, no HTML. // The subject prefix and the footer brand come from the form's // configuration; the defaults reproduce the earlier wording. func composeConfirmation(from, to, replyToOwner string, form *config.Form, link string) []byte { subject := "Confirm your subscription" if prefix := form.EmailSubjectPrefix(); prefix != "" { subject = fmt.Sprintf("[%s/%s] %s", prefix, form.Name, subject) } var body bytes.Buffer fmt.Fprintf(&body, "Hi,\r\n\r\n") fmt.Fprintf(&body, "someone signed this address up for the \"%s\" form.\r\n", form.Name) fmt.Fprintf(&body, "If that was you, please confirm the subscription by opening:\r\n\r\n") fmt.Fprintf(&body, " %s\r\n\r\n", link) fmt.Fprintf(&body, "If it was not you, ignore this message and nothing will happen:\r\n") fmt.Fprintf(&body, "the request expires automatically without any action from you.\r\n\r\n") if brand := form.Brand(); brand != "" { fmt.Fprintf(&body, "Delivered by %s\r\n", brand) } var msg bytes.Buffer msg.WriteString(fmt.Sprintf("From: %s\r\n", sanitizeHeaderValue(from))) msg.WriteString(fmt.Sprintf("To: %s\r\n", sanitizeHeaderValue(to))) msg.WriteString(fmt.Sprintf("Subject: %s\r\n", sanitizeHeaderValue(subject))) msg.WriteString(fmt.Sprintf("Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))) msg.WriteString(fmt.Sprintf("Reply-To: %s\r\n", sanitizeHeaderValue(replyToOwner))) msg.WriteString("MIME-Version: 1.0\r\n") msg.WriteString("Content-Type: text/plain; charset=UTF-8\r\n") msg.WriteString("Content-Transfer-Encoding: 8bit\r\n") msg.WriteString("\r\n") msg.Write(body.Bytes()) return msg.Bytes() }