internal/mail/mail.go

4df013a904a7207b10a91b2984bf6402ba410263
gitbay/internal/mail/mail.go history · blame · raw

65 lines · 1658 bytes

 1// Package mail sends transactional email over SMTP: verification codes and
 2// invites. STARTTLS is used when the server offers it; PLAIN auth when
 3// credentials are configured.
 4package mail
 5
 6import (
 7	"crypto/tls"
 8	"fmt"
 9	"net"
10	"net/smtp"
11	"strings"
12	"time"
13
14	"gitbay.org/gitbay/internal/config"
15)
16
17// Send delivers one plain-text message. cfg.Mail.SMTPHost is host:port.
18func Send(cfg config.Config, to, subject, body string) error {
19	m := cfg.Mail
20	if m.SMTPHost == "" || m.From == "" {
21		return fmt.Errorf("[mail] smtp_host and from must be configured")
22	}
23	host := m.SMTPHost
24	if !strings.Contains(host, ":") {
25		host += ":587"
26	}
27	hostname, _, _ := net.SplitHostPort(host)
28
29	msg := strings.NewReplacer("\n", "\r\n").Replace(fmt.Sprintf(
30		"From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\n\n%s\n",
31		m.From, to, subject, time.Now().Format(time.RFC1123Z), body))
32
33	c, err := smtp.Dial(host)
34	if err != nil {
35		return fmt.Errorf("smtp dial %s: %w", host, err)
36	}
37	defer c.Close()
38	if ok, _ := c.Extension("STARTTLS"); ok {
39		if err := c.StartTLS(&tls.Config{ServerName: hostname}); err != nil {
40			return fmt.Errorf("starttls: %w", err)
41		}
42	}
43	if m.SMTPUser != "" {
44		if err := c.Auth(smtp.PlainAuth("", m.SMTPUser, m.SMTPPass, hostname)); err != nil {
45			return fmt.Errorf("smtp auth: %w", err)
46		}
47	}
48	if err := c.Mail(m.From); err != nil {
49		return err
50	}
51	if err := c.Rcpt(to); err != nil {
52		return err
53	}
54	w, err := c.Data()
55	if err != nil {
56		return err
57	}
58	if _, err := w.Write([]byte(msg)); err != nil {
59		return err
60	}
61	if err := w.Close(); err != nil {
62		return err
63	}
64	return c.Quit()
65}