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 } }