Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
175 lines
6.1 KiB
Go
Executable File
175 lines
6.1 KiB
Go
Executable File
package mailimp
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"time"
|
|
|
|
"github.com/tech/sendico/notification/interface/api/localizer"
|
|
mb "github.com/tech/sendico/notification/internal/server/notificationimp/mail/internal/builder"
|
|
"github.com/tech/sendico/notification/internal/server/notificationimp/mail/internal/mailkey"
|
|
mmail "github.com/tech/sendico/notification/internal/server/notificationimp/mail/messagebuilder"
|
|
"github.com/tech/sendico/pkg/domainprovider"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
mutil "github.com/tech/sendico/pkg/mutil/config"
|
|
mduration "github.com/tech/sendico/pkg/mutil/duration"
|
|
mail "github.com/xhit/go-simple-mail/v2"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Client implements a mail client
|
|
type Client struct {
|
|
logger mlogger.Logger
|
|
server *mail.SMTPServer
|
|
client *mail.SMTPClient
|
|
from string
|
|
l localizer.Localizer
|
|
dp domainprovider.DomainProvider
|
|
}
|
|
|
|
// Config represents the mail configuration
|
|
type GSMConfig struct {
|
|
Username *string `mapstructure:"username,omitempty" yaml:"username,omitempty"`
|
|
UsernameEnv *string `mapstructure:"username_env,omitempty" yaml:"username_env,omitempty"`
|
|
Password *string `mapstructure:"password" yaml:"password"`
|
|
PasswordEnv *string `mapstructure:"password_env" yaml:"password_env"`
|
|
Host string `mapstructure:"host" yaml:"host"`
|
|
Port int `mapstructure:"port" yaml:"port"`
|
|
From string `mapstructure:"from" yaml:"from"`
|
|
TimeOut int `mapstructure:"network_timeout" yaml:"network_timeout"`
|
|
}
|
|
|
|
func (c *Client) sendImp(m mmail.Message, msg *mail.Email) error {
|
|
err := msg.Send(c.client)
|
|
if err != nil {
|
|
c.logger.Warn("Error sending email", zap.Error(err), zap.String("template_id", m.TemplateID()), zap.Strings("recipients", msg.GetRecipients()))
|
|
} else {
|
|
c.logger.Info("Email sent", zap.Strings("recipients", msg.GetRecipients()), zap.String("template_id", m.TemplateID()))
|
|
}
|
|
// TODO: add amplitude notification
|
|
return err
|
|
}
|
|
|
|
// Send sends an email message to the provided address and with the provided subject
|
|
func (c *Client) Send(r mmail.MailBuilder) error {
|
|
// New email simple html with inline and CC
|
|
|
|
r.AddData("ServiceName", c.l.ServiceName()).AddData("SupportMail", c.l.SupportMail())
|
|
m, err := r.Build()
|
|
if err != nil {
|
|
c.logger.Warn("Failed to build message", zap.Error(err))
|
|
return err
|
|
}
|
|
body, err := m.Body(c.l, c.dp)
|
|
if err != nil {
|
|
c.logger.Warn("Failed to build message body", zap.Error(err))
|
|
return err
|
|
}
|
|
if (len(body) == 0) || (len(m.Recipients()) == 0) {
|
|
c.logger.Warn("Malformed messge", zap.String("template_id", m.TemplateID()),
|
|
zap.String("locale", m.Locale()), zap.Strings("recipients", m.Recipients()),
|
|
zap.Int("body_size", len(body)))
|
|
return merrors.InvalidArgument("malformed message", "message.body", "message.recipients")
|
|
}
|
|
subj, err := mailkey.Subject(c.l, m.Parameters(), m.TemplateID(), m.Locale())
|
|
if err != nil {
|
|
c.logger.Warn("Failed to localize subject", zap.Error(err), zap.String("template_id", m.TemplateID()),
|
|
zap.String("locale", m.Locale()), zap.Strings("recipients", m.Recipients()),
|
|
zap.Int("body_size", len(body)))
|
|
return err
|
|
}
|
|
msg := mail.NewMSG()
|
|
msg.SetFrom(c.from).
|
|
AddTo(m.Recipients()...).
|
|
SetSubject(subj).
|
|
SetBody(mail.TextHTML, body)
|
|
|
|
// Call Send and pass the client
|
|
if err = c.sendImp(m, msg); err != nil {
|
|
c.logger.Info("Failed to send an email, attempting to reconnect...",
|
|
zap.Error(err),
|
|
zap.String("template", m.TemplateID()), zap.Strings("addresses", m.Recipients()), zap.String("locale", m.Locale()))
|
|
|
|
c.client = nil
|
|
c.client, err = c.server.Connect()
|
|
if err != nil {
|
|
c.logger.Warn("Failed to reconnect mail client",
|
|
zap.Error(err),
|
|
zap.String("template", m.TemplateID()), zap.Strings("addresses", m.Recipients()), zap.String("locale", m.Locale()))
|
|
return err
|
|
}
|
|
c.logger.Info("Connection has been successfully restored",
|
|
zap.Error(err),
|
|
zap.String("template", m.TemplateID()), zap.Strings("addresses", m.Recipients()), zap.String("locale", m.Locale()))
|
|
|
|
err = c.sendImp(m, msg)
|
|
if err != nil {
|
|
c.logger.Warn("Failed to send message after mail client recreation",
|
|
zap.Error(err),
|
|
zap.String("template", m.TemplateID()), zap.Strings("addresses", m.Recipients()), zap.String("locale", m.Locale()))
|
|
return err
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *Client) MailBuilder() mmail.MailBuilder {
|
|
return mb.NewMessageBuilder()
|
|
}
|
|
|
|
// NewClient return a new mail
|
|
func NewClient(logger mlogger.Logger, l localizer.Localizer, dp domainprovider.DomainProvider, config *GSMConfig) *Client {
|
|
smtpServer := mail.NewSMTPClient()
|
|
|
|
// SMTP Server
|
|
smtpServer.Host = config.Host
|
|
if config.Port < 1 {
|
|
logger.Warn("Invalid mail client port configuration, defaulting to 465", zap.Int("port", config.Port))
|
|
config.Port = 465
|
|
}
|
|
smtpServer.Port = config.Port
|
|
smtpServer.Username = mutil.GetConfigValue(logger, "username", "username_env", config.Username, config.UsernameEnv)
|
|
smtpServer.Password = mutil.GetConfigValue(logger, "password", "password_env", config.Password, config.PasswordEnv)
|
|
smtpServer.Encryption = mail.EncryptionSSL
|
|
|
|
// Since v2.3.0 you can specified authentication type:
|
|
// - PLAIN (default)
|
|
// - LOGIN
|
|
// - CRAM-MD5
|
|
// server.Authentication = mail.AuthPlain
|
|
|
|
// Variable to keep alive connection
|
|
smtpServer.KeepAlive = true
|
|
|
|
// Timeout for connect to SMTP Server
|
|
smtpServer.ConnectTimeout = mduration.Param2Duration(config.TimeOut, time.Second)
|
|
|
|
// Timeout for send the data and wait respond
|
|
smtpServer.SendTimeout = mduration.Param2Duration(config.TimeOut, time.Second)
|
|
|
|
// Set TLSConfig to provide custom TLS configuration. For example,
|
|
// to skip TLS verification (useful for testing):
|
|
smtpServer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
|
|
// SMTP client
|
|
lg := logger.Named("client")
|
|
smtpClient, err := smtpServer.Connect()
|
|
if err != nil {
|
|
lg.Warn("Failed to connect", zap.Error(err))
|
|
} else {
|
|
lg.Info("Connected successfully", zap.String("username", smtpServer.Username), zap.String("host", config.Host))
|
|
}
|
|
|
|
from := config.From + " <" + smtpServer.Username + ">"
|
|
|
|
return &Client{
|
|
logger: lg,
|
|
server: smtpServer,
|
|
client: smtpClient,
|
|
from: from,
|
|
l: l,
|
|
dp: dp,
|
|
}
|
|
}
|