ParseSESNotification parses the SES event JSON (the decoded SNS Message body) into a normalized Event. Returns an error for malformed JSON or a missing message id; an unrecognized event kind yields KindOther with no recipient outcomes (caller no-ops).
(messageBody []byte)
| 76 | // missing message id; an unrecognized event kind yields KindOther with no |
| 77 | // recipient outcomes (caller no-ops). |
| 78 | func ParseSESNotification(messageBody []byte) (*Event, error) { |
| 79 | var n sesNotification |
| 80 | if err := json.Unmarshal(messageBody, &n); err != nil { |
| 81 | return nil, fmt.Errorf("parse SES notification: %w", err) |
| 82 | } |
| 83 | typ := n.EventType |
| 84 | if typ == "" { |
| 85 | typ = n.NotificationType |
| 86 | } |
| 87 | if n.Mail.MessageID == "" { |
| 88 | return nil, fmt.Errorf("SES notification missing mail.messageId") |
| 89 | } |
| 90 | ev := &Event{SESMessageID: n.Mail.MessageID} |
| 91 | |
| 92 | switch typ { |
| 93 | case "Delivery": |
| 94 | ev.Kind = KindDelivery |
| 95 | recips := n.Mail.Destination |
| 96 | if n.Delivery != nil && len(n.Delivery.Recipients) > 0 { |
| 97 | recips = n.Delivery.Recipients |
| 98 | } |
| 99 | for _, a := range recips { |
| 100 | ev.Recipients = append(ev.Recipients, RecipientOutcome{Address: norm(a), Status: StatusDelivered}) |
| 101 | } |
| 102 | case "Bounce": |
| 103 | ev.Kind = KindBounce |
| 104 | if n.Bounce != nil { |
| 105 | // Only a Permanent (hard) bounce suppresses; Transient/Undetermined |
| 106 | // are recorded as bounced but not auto-suppressed (decision 9: never |
| 107 | // suppress on a single unverified/soft signal). |
| 108 | hard := strings.EqualFold(n.Bounce.BounceType, "Permanent") |
| 109 | for _, r := range n.Bounce.BouncedRecipients { |
| 110 | ev.Recipients = append(ev.Recipients, RecipientOutcome{ |
| 111 | Address: norm(r.EmailAddress), Status: StatusBounced, |
| 112 | Detail: r.DiagnosticCode, Suppress: hard, |
| 113 | }) |
| 114 | } |
| 115 | } |
| 116 | case "Complaint": |
| 117 | ev.Kind = KindComplaint |
| 118 | if n.Complaint != nil { |
| 119 | for _, r := range n.Complaint.ComplainedRecipients { |
| 120 | ev.Recipients = append(ev.Recipients, RecipientOutcome{ |
| 121 | Address: norm(r.EmailAddress), Status: StatusComplained, |
| 122 | Detail: n.Complaint.ComplaintFeedbackType, Suppress: true, |
| 123 | }) |
| 124 | } |
| 125 | } |
| 126 | case "DeliveryDelay": |
| 127 | ev.Kind = KindDeliveryDelay |
| 128 | if n.DeliveryDelay != nil { |
| 129 | for _, r := range n.DeliveryDelay.DelayedRecipients { |
| 130 | ev.Recipients = append(ev.Recipients, RecipientOutcome{ |
| 131 | Address: norm(r.EmailAddress), Status: StatusDeferred, Detail: r.DiagnosticCode, |
| 132 | }) |
| 133 | } |
| 134 | } |
| 135 | case "Send": |