Files
mailx/aws/aws.go
T
2025-11-21 14:01:43 +08:00

207 lines
4.9 KiB
Go

package aws
import (
"context"
"errors"
"fmt"
"time"
"code.yun.ink/pkg/mailx/interfaces"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
const (
MaxRetries = 3
DefaultRegion = "us-east-1"
MaxRecipients = 50 // AWS SES limit
)
type Aws struct {
interfaces.DefaultEmail
sesClient *ses.SES
}
func NewAws() *Aws {
aws := &Aws{}
aws.Options = interfaces.DefaultOptions()
aws.EmailType = interfaces.EmailTypeAws
return aws
}
func (l *Aws) SetOption(ctx context.Context, opt ...interfaces.Option) (interfaces.EmailInterface, error) {
for _, o := range opt {
o(&l.Options)
}
if l.Options.Aws == nil {
return nil, fmt.Errorf("AWS configuration is required")
}
// 验证配置
if err := l.validateConfig(); err != nil {
return nil, fmt.Errorf("invalid AWS config: %w", err)
}
if l.Options.Aws.Region == "" {
l.Options.Aws.Region = DefaultRegion
}
// 初始化SES客户端
if err := l.initSESClient(); err != nil {
return nil, fmt.Errorf("failed to initialize SES client: %w", err)
}
// 安全日志输出
l.Options.Logger.Infof(ctx, "AWS SES configured - Region:%s Sender:%s",
l.Options.Aws.Region, l.Options.Aws.Sender)
return l, nil
}
func (l *Aws) Send(ctx context.Context, params interfaces.Message) error {
if l.sesClient == nil {
return fmt.Errorf("AWS SES client not initialized")
}
// 验证消息
if err := l.validateMessage(params); err != nil {
return fmt.Errorf("invalid message: %w", err)
}
// 重试机制
var lastErr error
for i := 0; i < MaxRetries; i++ {
if i > 0 {
l.Options.Logger.Infof(ctx, "Retrying AWS SES email send, attempt %d/%d", i+1, MaxRetries)
time.Sleep(time.Duration(i) * time.Second)
}
lastErr = l.sendEmail(ctx, params)
if lastErr == nil {
l.Options.Logger.Infof(ctx, "AWS SES email sent successfully to %v", params.To)
return nil
}
l.Options.Logger.Errorf(ctx, "AWS SES send attempt %d failed: %v", i+1, lastErr)
}
return fmt.Errorf("failed to send AWS SES email after %d attempts: %w", MaxRetries, lastErr)
}
// 验证配置
func (l *Aws) validateConfig() error {
if l.Options.Aws.AccessId == "" {
return errors.New("AccessId is required")
}
if l.Options.Aws.AccessSecret == "" {
return errors.New("AccessSecret is required")
}
if l.Options.Aws.Sender == "" {
return errors.New("Sender is required")
}
return nil
}
// 验证消息
func (l *Aws) validateMessage(params interfaces.Message) error {
if len(params.To) == 0 {
return errors.New("at least one recipient is required")
}
if len(params.To) > MaxRecipients {
return fmt.Errorf("too many recipients: %d (max: %d)", len(params.To), MaxRecipients)
}
if params.Subject == "" {
return errors.New("subject is required")
}
if params.Body == "" {
return errors.New("body is required")
}
return nil
}
// 初始化SES客户端
func (l *Aws) initSESClient() error {
config := aws.Config{
Region: aws.String(l.Options.Aws.Region),
Credentials: credentials.NewStaticCredentials(l.Options.Aws.AccessId, l.Options.Aws.AccessSecret, ""),
}
sess, err := session.NewSession(&config)
if err != nil {
return fmt.Errorf("failed to create AWS session: %w", err)
}
l.sesClient = ses.New(sess)
return nil
}
// 发送邮件核心逻辑
func (l *Aws) sendEmail(ctx context.Context, params interfaces.Message) error {
// 构建收件人列表
destination := &ses.Destination{}
if len(params.To) > 0 {
toAddresses := make([]*string, len(params.To))
for i, addr := range params.To {
toAddresses[i] = aws.String(addr)
}
destination.ToAddresses = toAddresses
}
if len(params.Cc) > 0 {
ccAddresses := make([]*string, len(params.Cc))
for i, addr := range params.Cc {
ccAddresses[i] = aws.String(addr)
}
destination.CcAddresses = ccAddresses
}
if len(params.Bcc) > 0 {
bccAddresses := make([]*string, len(params.Bcc))
for i, addr := range params.Bcc {
bccAddresses[i] = aws.String(addr)
}
destination.BccAddresses = bccAddresses
}
// 构建邮件内容
message := &ses.Message{
Subject: &ses.Content{
Data: aws.String(params.Subject),
Charset: aws.String("UTF-8"),
},
Body: &ses.Body{
Html: &ses.Content{
Data: aws.String(params.Body),
Charset: aws.String("UTF-8"),
},
},
}
// 发送邮件
input := &ses.SendEmailInput{
Destination: destination,
Message: message,
Source: aws.String(l.Options.Aws.Sender),
}
// 设置回复地址
if params.ReplyTo != "" {
input.ReplyToAddresses = []*string{aws.String(params.ReplyTo)}
}
resp, err := l.sesClient.SendEmailWithContext(ctx, input)
if err != nil {
return fmt.Errorf("AWS SES API call failed: %w", err)
}
// 记录响应信息
if resp != nil && resp.MessageId != nil {
l.Options.Logger.Infof(ctx, "AWS SES email sent - MessageId:%s", aws.StringValue(resp.MessageId))
}
return nil
}