@@ -0,0 +1,157 @@
|
||||
package bounce
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"source.offmarket.win/aleagle/eaglecast/internal/bounce/mailbox"
|
||||
"source.offmarket.win/aleagle/eaglecast/internal/bounce/webhooks"
|
||||
"source.offmarket.win/aleagle/eaglecast/models"
|
||||
)
|
||||
|
||||
// Mailbox represents a POP/IMAP mailbox client that can scan messages and pass
|
||||
// them to a given channel.
|
||||
type Mailbox interface {
|
||||
Scan(limit int, ch chan models.Bounce) error
|
||||
}
|
||||
|
||||
// Opt represents bounce processing options.
|
||||
type Opt struct {
|
||||
MailboxEnabled bool `json:"mailbox_enabled"`
|
||||
MailboxType string `json:"mailbox_type"`
|
||||
Mailbox mailbox.Opt `json:"mailbox"`
|
||||
WebhooksEnabled bool `json:"webhooks_enabled"`
|
||||
SESEnabled bool `json:"ses_enabled"`
|
||||
AzureEnabled bool `json:"azure_enabled"`
|
||||
AzureSharedSecret string `json:"azure_shared_secret"`
|
||||
AzureSharedSecretHeader string `json:"azure_shared_secret_header"`
|
||||
SendgridEnabled bool `json:"sendgrid_enabled"`
|
||||
SendgridKey string `json:"sendgrid_key"`
|
||||
Postmark struct {
|
||||
Enabled bool
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
ForwardEmail struct {
|
||||
Enabled bool
|
||||
Key string
|
||||
}
|
||||
Lettermint struct {
|
||||
Enabled bool
|
||||
Key string
|
||||
}
|
||||
|
||||
RecordBounceCB func(models.Bounce) error
|
||||
}
|
||||
|
||||
// Manager handles e-mail bounces.
|
||||
type Manager struct {
|
||||
queue chan models.Bounce
|
||||
mailbox Mailbox
|
||||
SES *webhooks.SES
|
||||
Azure *webhooks.Azure
|
||||
Sendgrid *webhooks.Sendgrid
|
||||
Postmark *webhooks.Postmark
|
||||
Forwardemail *webhooks.Forwardemail
|
||||
Lettermint *webhooks.Lettermint
|
||||
queries *Queries
|
||||
opt Opt
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
// Queries contains the queries.
|
||||
type Queries struct {
|
||||
DB *sqlx.DB
|
||||
RecordQuery *sqlx.Stmt
|
||||
}
|
||||
|
||||
// New returns a new instance of the bounce manager.
|
||||
func New(opt Opt, q *Queries, lo *log.Logger) (*Manager, error) {
|
||||
m := &Manager{
|
||||
opt: opt,
|
||||
queries: q,
|
||||
queue: make(chan models.Bounce, 1000),
|
||||
log: lo,
|
||||
}
|
||||
|
||||
// Is there a mailbox?
|
||||
if opt.MailboxEnabled {
|
||||
switch opt.MailboxType {
|
||||
case "pop":
|
||||
m.mailbox = mailbox.NewPOP(opt.Mailbox, lo)
|
||||
default:
|
||||
return nil, errors.New("unknown bounce mailbox type")
|
||||
}
|
||||
}
|
||||
|
||||
if opt.WebhooksEnabled {
|
||||
if opt.SESEnabled {
|
||||
m.SES = webhooks.NewSES()
|
||||
}
|
||||
if opt.AzureEnabled {
|
||||
m.Azure = webhooks.NewAzure(opt.AzureSharedSecret, opt.AzureSharedSecretHeader)
|
||||
}
|
||||
|
||||
if opt.SendgridEnabled {
|
||||
sg, err := webhooks.NewSendgrid(opt.SendgridKey)
|
||||
if err != nil {
|
||||
lo.Printf("error initializing sendgrid webhooks: %v", err)
|
||||
} else {
|
||||
m.Sendgrid = sg
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Postmark.Enabled {
|
||||
m.Postmark = webhooks.NewPostmark(opt.Postmark.Username, opt.Postmark.Password)
|
||||
}
|
||||
|
||||
if opt.ForwardEmail.Enabled {
|
||||
fe := webhooks.NewForwardemail([]byte(opt.ForwardEmail.Key))
|
||||
m.Forwardemail = fe
|
||||
}
|
||||
|
||||
if opt.Lettermint.Enabled {
|
||||
m.Lettermint = webhooks.NewLettermint([]byte(opt.Lettermint.Key))
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Run is a blocking function that listens for bounce events from webhooks and or mailboxes
|
||||
// and executes them on the DB.
|
||||
func (m *Manager) Run() {
|
||||
if m.opt.MailboxEnabled {
|
||||
go m.runMailboxScanner()
|
||||
}
|
||||
|
||||
for b := range m.queue {
|
||||
if b.CreatedAt.IsZero() {
|
||||
b.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
if err := m.opt.RecordBounceCB(b); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runMailboxScanner runs a blocking loop that scans the mailbox at given intervals.
|
||||
func (m *Manager) runMailboxScanner() {
|
||||
for {
|
||||
m.log.Printf("scanning bounce mailbox %s", m.opt.Mailbox.Host)
|
||||
if err := m.mailbox.Scan(1000, m.queue); err != nil {
|
||||
m.log.Printf("error scanning bounce mailbox: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(m.opt.Mailbox.ScanInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// Record records a new bounce event given the subscriber's email or UUID.
|
||||
func (m *Manager) Record(b models.Bounce) error {
|
||||
m.queue <- b
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user