//go:build linux || freebsd // Package email handles SMTP message composition and delivery. package email import ( "bytes" "fmt" "html/template" "net/smtp" "time" "sourcedock.dev/petrbalvin/nuntius/internal/config" "sourcedock.dev/petrbalvin/nuntius/pkg/contactform" ) // FormSender delivers contact form submissions for a single form // using its own SMTP credentials. Each form has its own FormSender // so credentials are isolated per tenant. type FormSender struct { form *config.Form } // NewFormSender returns a new FormSender bound to the given form. func NewFormSender(form *config.Form) *FormSender { return &FormSender{form: form} } // Send composes and sends a multi-part email (plain text + HTML) // for the given request. Returns an error if the SMTP round-trip fails. func (s *FormSender) Send(req contactform.Request) error { auth := smtp.PlainAuth("", s.form.SMTP.User, s.form.SMTP.Password, s.form.SMTP.Host) msg := compose(s.form.From, s.form.To, req, s.form.Name, s.form.Type) return smtp.SendMail(s.form.SMTP.AddrFor(), auth, s.form.From, []string{s.form.To}, msg) } // emailTemplateContact is the HTML body template for contact-type forms. var emailTemplateContact = template.Must(template.New("contact").Parse(`
Email generation error: %v
", err) } // --- Subject + plain-text body per form type --- var subject, text string switch formType { case "newsletter": subject = fmt.Sprintf("[nuntius/%s] New newsletter subscriber", formName) text = fmt.Sprintf( "New Newsletter Subscriber: %s\r\n"+ "---\r\n"+ "Email: %s\r\n"+ "Received: %s\r\n"+ "\r\n"+ "Delivered by nuntius\r\n", formName, req.Email, received, ) case "feedback": subject = fmt.Sprintf("[nuntius/%s] New feedback", formName) text = fmt.Sprintf( "New Feedback: %s\r\n"+ "---\r\n"+ "From: %s <%s>\r\n"+ "Received: %s\r\n"+ "\r\n"+ "%s\r\n\r\n"+ "Delivered by nuntius\r\n", formName, req.Name, req.Email, received, req.Message, ) case "generic": subject = fmt.Sprintf("[nuntius/%s] New submission", formName) text = fmt.Sprintf( "New Submission: %s\r\n"+ "---\r\n"+ "From: %s <%s>\r\n"+ "Received: %s\r\n"+ "\r\n"+ "%s\r\n\r\n"+ "Delivered by nuntius\r\n", formName, req.Name, req.Email, received, req.Message, ) default: // contact subject = fmt.Sprintf("[nuntius/%s] Contact form submission", formName) if req.Service != "" { subject = fmt.Sprintf("[nuntius/%s][%s] Contact form submission", formName, req.Service) } text = fmt.Sprintf( "Contact Form Submission: %s\r\n"+ "---\r\n"+ "From: %s <%s>\r\n"+ "Service interest: %s\r\n"+ "Received: %s\r\n"+ "\r\n"+ "%s\r\n\r\n"+ "Delivered by nuntius\r\n", formName, req.Name, req.Email, req.Service, received, req.Message, ) } // Assemble multipart/alternative message. boundary := "nuntius-boundary" var msg bytes.Buffer msg.WriteString(fmt.Sprintf("From: %s\r\n", from)) msg.WriteString(fmt.Sprintf("To: %s\r\n", to)) msg.WriteString(fmt.Sprintf("Subject: %s\r\n", subject)) msg.WriteString(fmt.Sprintf("Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))) msg.WriteString(fmt.Sprintf("Reply-To: %s\r\n", req.Email)) msg.WriteString("MIME-Version: 1.0\r\n") msg.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=%s\r\n", boundary)) msg.WriteString("\r\n") msg.WriteString(fmt.Sprintf("--%s\r\n", boundary)) msg.WriteString("Content-Type: text/plain; charset=UTF-8\r\n") msg.WriteString("\r\n") msg.WriteString(text) msg.WriteString("\r\n") msg.WriteString(fmt.Sprintf("--%s\r\n", boundary)) msg.WriteString("Content-Type: text/html; charset=UTF-8\r\n") msg.WriteString("\r\n") msg.WriteString(htmlBuf.String()) msg.WriteString("\r\n") msg.WriteString(fmt.Sprintf("--%s--\r\n", boundary)) return msg.Bytes() }