knadh/listmonk · error

error unmarshalling SES notification: %v

Error message

error unmarshalling SES notification: %v

What it means

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.)

Source

Thrown at internal/bounce/webhooks/ses.go:116

	if err != nil {
		return fmt.Errorf("error requesting subscription URL: %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("non 200 response on subscription URL: %v", resp.StatusCode)
	}

	return nil
}

// ProcessBounce processes an SES bounce notification and returns a Bounce object.
func (s *SES) ProcessBounce(b []byte) (models.Bounce, error) {
	var (
		bounce models.Bounce
		n      sesNotif
	)
	if err := json.Unmarshal(b, &n); err != nil {
		return bounce, fmt.Errorf("error unmarshalling SES notification: %v", err)
	}
	if err := s.verifyNotif(n); err != nil {
		return bounce, err
	}

	var m sesMail
	if err := json.Unmarshal([]byte(n.Message), &m); err != nil {
		return bounce, fmt.Errorf("error unmarshalling SES notification: %v", err)
	}

	if (m.EventType != "" && m.EventType != "Bounce") ||
		(m.NotifType != "" && (m.NotifType != "Bounce" && m.NotifType != "Complaint")) {
		return bounce, errors.New("notification type is not bounce")
	}

	if len(m.Mail.Destination) == 0 {
		return bounce, errors.New("no destination e-mails found in SES notification")
	}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Log the raw request body and validate it as an SNS envelope JSON (Type, MessageId, TopicArn, Message, Signature, SigningCertURL)
  2. Ensure SNS raw message delivery is DISABLED for the subscription so the full SNS envelope is delivered
  3. Verify the subscription points at the SES bounce route and that Message contains valid SES event JSON
  4. When testing manually, POST a complete signed SNS envelope rather than only the inner SES event

Example fix

// before (raw SES event posted directly)
{"eventType":"Bounce",...}
// after (SNS envelope)
{"Type":"Notification","MessageId":"id","TopicArn":"arn:aws:sns:...","Message":"{\"eventType\":\"Bounce\",...}","Timestamp":"...","SignatureVersion":"1","Signature":"...","SigningCertURL":"https://sns...pem"}
Defensive patterns

Strategy: validation

Validate before calling

func validSESNotification(b []byte) bool {
    var probe struct {
        Type    string `json:"Type"`
        Message string `json:"Message"`
    }
    if json.Unmarshal(b, &probe) != nil { return false }
    if probe.Type != "Notification" || probe.Message == "" { return false }
    var mail struct {
        EventType string `json:"eventType"`
        NotifType string `json:"notificationType"`
    }
    return json.Unmarshal([]byte(probe.Message), &mail) == nil
}
// pre-validate envelope + inner Message before calling ProcessBounce

Try / catch

bounce, err := handler.ProcessBounce(body)
if err != nil {
    if strings.HasPrefix(err.Error(), "error unmarshalling SES notification") {
        log.Printf("SES raw body: %s", string(body))
        http.Error(w, "invalid SES payload", http.StatusBadRequest)
        return
    }
    http.Error(w, "webhook error", http.StatusInternalServerError)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/377709d5878d8965. Report an issue: GitHub.