Files
fm_be/internal/service/ses_event.go
2026-07-16 22:16:45 +07:00

103 lines
3.2 KiB
Go

package service
import (
"encoding/json"
"errors"
"strings"
"time"
)
var ErrInvalidSESEvent = errors.New("invalid ses event notification")
type SESEventNotification struct {
EventType string `json:"eventType"`
Mail SESEventMail `json:"mail"`
Bounce *SESBounceEvent `json:"bounce,omitempty"`
Complaint *SESComplaintEvent `json:"complaint,omitempty"`
Delivery *SESDeliveryEvent `json:"delivery,omitempty"`
Metadata map[string]any `json:"-"`
RawPayload map[string]json.RawMessage `json:"-"`
}
type SESEventMail struct {
Timestamp time.Time `json:"timestamp"`
MessageID string `json:"messageId"`
Source string `json:"source"`
Destination []string `json:"destination"`
Tags map[string][]string `json:"tags,omitempty"`
ConfigurationSet string `json:"configurationSet,omitempty"`
}
type SESBounceEvent struct {
BounceType string `json:"bounceType,omitempty"`
BounceSubType string `json:"bounceSubType,omitempty"`
ReportingMTA string `json:"reportingMTA,omitempty"`
RemoteMTAIP string `json:"remoteMtaIp,omitempty"`
FeedbackID string `json:"feedbackId,omitempty"`
FeedbackSourceArn string `json:"feedbackSourceArn,omitempty"`
}
type SESComplaintEvent struct {
ComplaintFeedbackType string `json:"complaintFeedbackType,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
FeedbackID string `json:"feedbackId,omitempty"`
}
type SESDeliveryEvent struct {
Timestamp time.Time `json:"timestamp"`
ProcessingTimeMillis int64 `json:"processingTimeMillis,omitempty"`
ReportingMTA string `json:"reportingMTA,omitempty"`
SMTPResponse string `json:"smtpResponse,omitempty"`
RemoteMTAIP string `json:"remoteMtaIp,omitempty"`
}
type snsEnvelope struct {
Type string `json:"Type"`
Message string `json:"Message"`
}
func ParseSESEventNotification(body []byte) (*SESEventNotification, error) {
body = []byte(strings.TrimSpace(string(body)))
if len(body) == 0 {
return nil, ErrInvalidSESEvent
}
if inner, ok := unwrapSNSMessage(body); ok {
body = inner
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(body, &raw); err != nil {
return nil, errors.Join(ErrInvalidSESEvent, err)
}
var event SESEventNotification
if err := json.Unmarshal(body, &event); err != nil {
return nil, errors.Join(ErrInvalidSESEvent, err)
}
if strings.TrimSpace(event.EventType) == "" {
return nil, errors.Join(ErrInvalidSESEvent, errors.New("event type is required"))
}
if strings.TrimSpace(event.Mail.MessageID) == "" {
return nil, errors.Join(ErrInvalidSESEvent, errors.New("mail message id is required"))
}
event.RawPayload = raw
return &event, nil
}
func unwrapSNSMessage(body []byte) ([]byte, bool) {
var envelope snsEnvelope
if err := json.Unmarshal(body, &envelope); err != nil {
return nil, false
}
if strings.TrimSpace(envelope.Message) == "" {
return nil, false
}
switch strings.TrimSpace(envelope.Type) {
case "Notification", "":
return []byte(envelope.Message), true
default:
return nil, false
}
}