103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
|
|
package email
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||
|
|
"github.com/aws/aws-sdk-go-v2/service/ses"
|
||
|
|
"github.com/aws/aws-sdk-go-v2/service/ses/types"
|
||
|
|
)
|
||
|
|
|
||
|
|
// SESSettings configures AWS SES (ported from app-cloudep-notification-service).
|
||
|
|
type SESSettings struct {
|
||
|
|
Sort int
|
||
|
|
Region string
|
||
|
|
AccessKey string
|
||
|
|
SecretKey string
|
||
|
|
SessionToken string
|
||
|
|
SendTimeout time.Duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// SESSender delivers email via AWS SES.
|
||
|
|
type SESSender struct {
|
||
|
|
name string
|
||
|
|
sort int
|
||
|
|
client *ses.Client
|
||
|
|
timeout time.Duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewSESSender builds an SES provider.
|
||
|
|
func NewSESSender(cfg SESSettings) (*SESSender, error) {
|
||
|
|
if cfg.Region == "" {
|
||
|
|
return nil, fmt.Errorf("email ses: region is required")
|
||
|
|
}
|
||
|
|
if cfg.AccessKey == "" || cfg.SecretKey == "" {
|
||
|
|
return nil, fmt.Errorf("email ses: access key and secret key are required")
|
||
|
|
}
|
||
|
|
timeout := cfg.SendTimeout
|
||
|
|
if timeout <= 0 {
|
||
|
|
timeout = 90 * time.Second
|
||
|
|
}
|
||
|
|
|
||
|
|
awsCfg := aws.Config{
|
||
|
|
Region: cfg.Region,
|
||
|
|
Credentials: credentials.NewStaticCredentialsProvider(
|
||
|
|
cfg.AccessKey,
|
||
|
|
cfg.SecretKey,
|
||
|
|
cfg.SessionToken,
|
||
|
|
),
|
||
|
|
}
|
||
|
|
|
||
|
|
return &SESSender{
|
||
|
|
name: "ses",
|
||
|
|
sort: cfg.Sort,
|
||
|
|
client: ses.NewFromConfig(awsCfg),
|
||
|
|
timeout: timeout,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SESSender) Name() string { return s.name }
|
||
|
|
func (s *SESSender) Sort() int { return s.sort }
|
||
|
|
|
||
|
|
func (s *SESSender) Send(ctx context.Context, msg *Message) (string, error) {
|
||
|
|
if msg == nil || len(msg.To) == 0 {
|
||
|
|
return "", fmt.Errorf("email ses: message or recipients missing")
|
||
|
|
}
|
||
|
|
from := msg.From
|
||
|
|
if from == "" {
|
||
|
|
return "", fmt.Errorf("email ses: from address is required")
|
||
|
|
}
|
||
|
|
|
||
|
|
sendCtx, cancel := context.WithTimeout(ctx, s.timeout)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
out, err := s.client.SendEmail(sendCtx, &ses.SendEmailInput{
|
||
|
|
Source: aws.String(from),
|
||
|
|
Destination: &types.Destination{
|
||
|
|
ToAddresses: append([]string(nil), msg.To...),
|
||
|
|
},
|
||
|
|
Message: &types.Message{
|
||
|
|
Subject: &types.Content{
|
||
|
|
Charset: aws.String("UTF-8"),
|
||
|
|
Data: aws.String(msg.Subject),
|
||
|
|
},
|
||
|
|
Body: &types.Body{
|
||
|
|
Html: &types.Content{
|
||
|
|
Charset: aws.String("UTF-8"),
|
||
|
|
Data: aws.String(msg.Body),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("email ses: %w", err)
|
||
|
|
}
|
||
|
|
if out.MessageId == nil || *out.MessageId == "" {
|
||
|
|
return "", fmt.Errorf("email ses: empty message id")
|
||
|
|
}
|
||
|
|
return *out.MessageId, nil
|
||
|
|
}
|