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 }