Newer
Older
wg-portal / internal / common / email.go
@Santiago De la Cruz Santiago De la Cruz on 30 Apr 2021 3 KB Fix email encryption type SSL/TLS
package common

import (
	"crypto/tls"
	"io"
	"io/ioutil"
	"time"

	"github.com/pkg/errors"
	mail "github.com/xhit/go-simple-mail/v2"
)

type MailEncryption string

const (
	MailEncryptionNone     MailEncryption = "none"
	MailEncryptionTLS      MailEncryption = "tls"
	MailEncryptionStartTLS MailEncryption = "starttls"
)

type MailAuthType string

const (
	MailAuthPlain   MailAuthType = "plain"
	MailAuthLogin   MailAuthType = "login"
	MailAuthCramMD5 MailAuthType = "crammd5"
)

type MailConfig struct {
	Host           string         `yaml:"host" envconfig:"EMAIL_HOST"`
	Port           int            `yaml:"port" envconfig:"EMAIL_PORT"`
	TLS            bool           `yaml:"tls" envconfig:"EMAIL_TLS"` // Deprecated, use MailConfig.Encryption instead.
	Encryption     MailEncryption `yaml:"encryption" envconfig:"EMAIL_ENCRYPTION"`
	CertValidation bool           `yaml:"certcheck" envconfig:"EMAIL_CERT_VALIDATION"`
	Username       string         `yaml:"user" envconfig:"EMAIL_USERNAME"`
	Password       string         `yaml:"pass" envconfig:"EMAIL_PASSWORD"`
	AuthType       MailAuthType   `yaml:"auth" envconfig:"EMAIL_AUTHTYPE"`
}

type MailAttachment struct {
	Name        string
	ContentType string
	Data        io.Reader
	Embedded    bool
}

// SendEmailWithAttachments sends a mail with optional attachments.
func SendEmailWithAttachments(cfg MailConfig, sender, replyTo, subject, body, htmlBody string, receivers []string, attachments []MailAttachment) error {
	srv := mail.NewSMTPClient()

	srv.ConnectTimeout = 30 * time.Second
	srv.SendTimeout = 30 * time.Second
	srv.Host = cfg.Host
	srv.Port = cfg.Port
	srv.Username = cfg.Username
	srv.Password = cfg.Password

	// TODO: remove this once the deprecated MailConfig.TLS config option has been removed
	if cfg.TLS {
		cfg.Encryption = MailEncryptionStartTLS
	}
	switch cfg.Encryption {
	case MailEncryptionTLS:
		srv.Encryption = mail.EncryptionSSLTLS
	case MailEncryptionStartTLS:
		srv.Encryption = mail.EncryptionSTARTTLS
	default: // MailEncryptionNone
		srv.Encryption = mail.EncryptionNone
	}
	srv.TLSConfig = &tls.Config{ServerName: srv.Host, InsecureSkipVerify: !cfg.CertValidation}
	switch cfg.AuthType {
	case MailAuthPlain:
		srv.Authentication = mail.AuthPlain
	case MailAuthLogin:
		srv.Authentication = mail.AuthLogin
	case MailAuthCramMD5:
		srv.Authentication = mail.AuthCRAMMD5
	}

	client, err := srv.Connect()
	if err != nil {
		return errors.Wrap(err, "failed to connect via SMTP")
	}

	if replyTo == "" {
		replyTo = sender
	}

	email := mail.NewMSG()
	email.SetFrom(sender).
		AddTo(receivers...).
		SetReplyTo(replyTo).
		SetSubject(subject)

	email.SetBody(mail.TextHTML, htmlBody)
	email.AddAlternative(mail.TextPlain, body)

	for _, attachment := range attachments {
		attachmentData, err := ioutil.ReadAll(attachment.Data)
		if err != nil {
			return errors.Wrapf(err, "failed to read attachment data for %s", attachment.Name)
		}

		if attachment.Embedded {
			email.AddInlineData(attachmentData, attachment.Name, attachment.ContentType)
		} else {
			email.AddAttachmentData(attachmentData, attachment.Name, attachment.ContentType)
		}
	}

	// Call Send and pass the client
	err = email.Send(client)
	if err != nil {
		return errors.Wrapf(err, "failed to send email")
	}
	return nil
}