storj/satellite/mailservice/service.go

150 lines
4.0 KiB
Go
Raw Normal View History

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information
package mailservice
import (
"bytes"
"context"
htmltemplate "html/template"
"path/filepath"
"sync"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/internal/post"
)
// Config defines values needed by mailservice service
type Config struct {
SMTPServerAddress string `help:"smtp server address" default:""`
TemplatePath string `help:"path to email templates source" default:""`
From string `help:"sender email address" default:""`
AuthType string `help:"smtp authentication type" releaseDefault:"login" devDefault:"simulate"`
Login string `help:"plain/login auth user login" default:""`
Password string `help:"plain/login auth user password" default:""`
RefreshToken string `help:"refresh token used to retrieve new access token" default:""`
ClientID string `help:"oauth2 app's client id" default:""`
ClientSecret string `help:"oauth2 app's client secret" default:""`
TokenURI string `help:"uri which is used when retrieving new access token" default:""`
}
var (
mon = monkit.Package()
)
// Sender sends emails
type Sender interface {
SendEmail(ctx context.Context, msg *post.Message) error
FromAddress() post.Address
}
// Message defines mailservice template-backed message for SendRendered method
type Message interface {
Template() string
Subject() string
}
// Service sends template-backed email messages through SMTP
type Service struct {
log *zap.Logger
sender Sender
html *htmltemplate.Template
// TODO(yar): prepare plain text version
//text *texttemplate.Template
sending sync.WaitGroup
}
// New creates new service
func New(log *zap.Logger, sender Sender, templatePath string) (*Service, error) {
var err error
service := &Service{log: log, sender: sender}
// TODO(yar): prepare plain text version
//service.text, err = texttemplate.ParseGlob(filepath.Join(templatePath, "*.txt"))
//if err != nil {
// return nil, err
//}
service.html, err = htmltemplate.ParseGlob(filepath.Join(templatePath, "*.html"))
if err != nil {
return nil, err
}
return service, nil
}
// Close closes and waits for any pending actions.
func (service *Service) Close() error {
service.sending.Wait()
return nil
}
// Send is generalized method for sending custom email message
func (service *Service) Send(ctx context.Context, msg *post.Message) (err error) {
defer mon.Task()(&ctx)(&err)
return service.sender.SendEmail(ctx, msg)
}
// SendRenderedAsync renders content from htmltemplate and texttemplate templates then sends it asynchronously
func (service *Service) SendRenderedAsync(ctx context.Context, to []post.Address, msg Message) {
// TODO: think of a better solution
service.sending.Add(1)
go func() {
defer service.sending.Done()
_ = service.SendRendered(ctx, to, msg)
}()
}
// SendRendered renders content from htmltemplate and texttemplate templates then sends it
func (service *Service) SendRendered(ctx context.Context, to []post.Address, msg Message) (err error) {
defer mon.Task()(&ctx)(&err)
var htmlBuffer bytes.Buffer
var textBuffer bytes.Buffer
// TODO(yar): prepare plain text version
//if err = service.text.ExecuteTemplate(&textBuffer, msg.Template() + ".txt", msg); err != nil {
// return
//}
if err = service.html.ExecuteTemplate(&htmlBuffer, msg.Template()+".html", msg); err != nil {
return
}
m := &post.Message{
From: service.sender.FromAddress(),
To: to,
Subject: msg.Subject(),
PlainText: textBuffer.String(),
Parts: []post.Part{
{
Type: "text/html; charset=UTF-8",
Content: htmlBuffer.String(),
},
},
}
err = service.sender.SendEmail(ctx, m)
// log error
var recipients []string
for _, recipient := range to {
recipients = append(recipients, recipient.String())
}
if err != nil {
service.log.Error("fail sending email",
zap.Strings("recipients", recipients),
zap.Error(err))
} else {
service.log.Info("email sent successfully",
zap.Strings("recipients", recipients))
}
return err
}