{"record":{"id":"377709d5878d8965","repo":"knadh/listmonk","slug":"error-unmarshalling-ses-notification-v","errorCode":null,"errorMessage":"error unmarshalling SES notification: %v","messagePattern":"error unmarshalling SES notification: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bounce/webhooks/ses.go","lineNumber":116,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"error requesting subscription URL: %v\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response on subscription URL: %v\", resp.StatusCode)\n\t}\n\n\treturn nil\n}\n\n// ProcessBounce processes an SES bounce notification and returns a Bounce object.\nfunc (s *SES) ProcessBounce(b []byte) (models.Bounce, error) {\n\tvar (\n\t\tbounce models.Bounce\n\t\tn      sesNotif\n\t)\n\tif err := json.Unmarshal(b, &n); err != nil {\n\t\treturn bounce, fmt.Errorf(\"error unmarshalling SES notification: %v\", err)\n\t}\n\tif err := s.verifyNotif(n); err != nil {\n\t\treturn bounce, err\n\t}\n\n\tvar m sesMail\n\tif err := json.Unmarshal([]byte(n.Message), &m); err != nil {\n\t\treturn bounce, fmt.Errorf(\"error unmarshalling SES notification: %v\", err)\n\t}\n\n\tif (m.EventType != \"\" && m.EventType != \"Bounce\") ||\n\t\t(m.NotifType != \"\" && (m.NotifType != \"Bounce\" && m.NotifType != \"Complaint\")) {\n\t\treturn bounce, errors.New(\"notification type is not bounce\")\n\t}\n\n\tif len(m.Mail.Destination) == 0 {\n\t\treturn bounce, errors.New(\"no destination e-mails found in SES notification\")\n\t}","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/ses.go#L98-L134","documentation":"SES.ProcessBounce JSON-unmarshals the raw SNS POST body into sesNotif before signature verification. If the body is not valid JSON matching the SNS envelope shape, it returns 'error unmarshalling SES notification'. (The same message is reused if the inner n.Message JSON fails to unmarshal into sesMail.)","triggerScenarios":"The SES webhook receives a body that fails json.Unmarshal into sesNotif: empty/HTML body, malformed JSON, non-SNS JSON payload, or wrong types for envelope fields; also when n.Message (the embedded SES event string) is not valid JSON for sesMail.","commonSituations":"Testing the endpoint with arbitrary JSON; a proxy returning an error page as body; pointing the SNS topic at the wrong route; SNS raw-message delivery enabled so the envelope shape differs; SES event format changes adding unexpected field types inside Message.","solutions":["Log the raw request body and validate it as an SNS envelope JSON (Type, MessageId, TopicArn, Message, Signature, SigningCertURL)","Ensure SNS raw message delivery is DISABLED for the subscription so the full SNS envelope is delivered","Verify the subscription points at the SES bounce route and that Message contains valid SES event JSON","When testing manually, POST a complete signed SNS envelope rather than only the inner SES event"],"exampleFix":"// before (raw SES event posted directly)\n{\"eventType\":\"Bounce\",...}\n// after (SNS envelope)\n{\"Type\":\"Notification\",\"MessageId\":\"id\",\"TopicArn\":\"arn:aws:sns:...\",\"Message\":\"{\\\"eventType\\\":\\\"Bounce\\\",...}\",\"Timestamp\":\"...\",\"SignatureVersion\":\"1\",\"Signature\":\"...\",\"SigningCertURL\":\"https://sns...pem\"}","handlingStrategy":"validation","validationCode":"func validSESNotification(b []byte) bool {\n    var probe struct {\n        Type    string `json:\"Type\"`\n        Message string `json:\"Message\"`\n    }\n    if json.Unmarshal(b, &probe) != nil { return false }\n    if probe.Type != \"Notification\" || probe.Message == \"\" { return false }\n    var mail struct {\n        EventType string `json:\"eventType\"`\n        NotifType string `json:\"notificationType\"`\n    }\n    return json.Unmarshal([]byte(probe.Message), &mail) == nil\n}\n// pre-validate envelope + inner Message before calling ProcessBounce","typeGuard":null,"tryCatchPattern":"bounce, err := handler.ProcessBounce(body)\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"error unmarshalling SES notification\") {\n        log.Printf(\"SES raw body: %s\", string(body))\n        http.Error(w, \"invalid SES payload\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, \"webhook error\", http.StatusInternalServerError)\n}","preventionTips":["Disable SNS raw message delivery so the full envelope wraps the SES event","Ensure Message contains a valid SES event JSON (eventType/notificationType)","Subscribe the topic to the correct SES bounce route","Test with real signed SNS notifications, not bare SES event JSON"],"tags":["webhook","json","unmarshal","aws","ses","go"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}