knadh/listmonk · error

error unmarshalling postmark notification: %v

Error message

error unmarshalling postmark notification: %v

What it means

Postmark's ProcessBounce first verifies basic-auth, then JSON-unmarshals the raw request body into postmarkNotif. If the body is not valid JSON or its fields don't match expected types (e.g. a string where an int is expected), the webhook returns 'error unmarshalling postmark notification'.

Source

Thrown at internal/bounce/webhooks/postmark.go:61

func NewPostmark(username, password string) *Postmark {
	return &Postmark{
		authHandler: middleware.BasicAuth(makePostmarkAuthHandler(username, password))(func(c echo.Context) error {
			return nil
		}),
	}
}

// ProcessBounce processes Postmark bounce notifications and returns one object.
func (p *Postmark) ProcessBounce(b []byte, c echo.Context) ([]models.Bounce, error) {
	// Do basicauth.
	if err := p.authHandler(c); err != nil {
		return nil, err
	}

	var n postmarkNotif
	if err := json.Unmarshal(b, &n); err != nil {
		return nil, fmt.Errorf("error unmarshalling postmark notification: %v", err)
	}

	// Ignore irrelevant messages.
	if n.RecordType != "Bounce" && n.RecordType != "SpamComplaint" {
		return nil, nil
	}

	supportedBounceType := true
	typ := models.BounceTypeHard
	switch n.Type {
	case "HardBounce", "BadEmailAddress", "ManuallyDeactivated":
		typ = models.BounceTypeHard
	case "SoftBounce", "Transient", "DnsError", "SpamNotification", "VirusNotification", "DMARCPolicy":
		typ = models.BounceTypeSoft
	case "SpamComplaint":
		typ = models.BounceTypeComplaint
	default:
		supportedBounceType = false

View on GitHub (pinned to 670c01717d)

Solutions

  1. Log the raw request body when this error occurs and validate it with a JSON linter
  2. Verify Postmark webhook is configured to POST application/json to the correct listmonk bounce URL
  3. Send a representative Postmark Bounce payload (with RecordType, ID as number, Metadata as string map) when testing
  4. Check for proxies/load balancers returning HTML error bodies to the endpoint

Example fix

// before
curl -X POST .../bounce -d 'RecordType=Bounce'
// after
curl -X POST .../bounce -H 'Content-Type: application/json' -d '{"RecordType":"Bounce","ID":1,"Type":"HardBounce","Email":"a@b.c","BouncedAt":"2024-01-01T00:00:00Z","Metadata":{}}'
Defensive patterns

Strategy: validation

Validate before calling

func validPostmarkBody(b []byte) bool {
    var probe struct {
        RecordType string          `json:"RecordType"`
        ID         int             `json:"ID"`
        Type       string          `json:"Type"`
        Email      string          `json:"Email"`
        Metadata   map[string]string `json:"Metadata"`
    }
    return json.Unmarshal(b, &probe) == nil && probe.RecordType != ""
}
// return 400 with a clear message if !validPostmarkBody(body)

Try / catch

bounces, err := handler.ProcessBounce(body, c)
if err != nil {
    if strings.HasPrefix(err.Error(), "error unmarshalling postmark notification") {
        c.Logger().Errorf("postmark body: %s", string(body))
        return echo.NewHTTPError(http.StatusBadRequest, "invalid JSON payload")
    }
    return err
}

Prevention

When it happens

Trigger: The Postmark webhook endpoint receives a body that fails json.Unmarshal: empty body, HTML error page from a misrouted request, form-encoded data, malformed JSON, or a JSON body with wrong types for fields like ID (int) or Metadata (map[string]string).

Common situations: Testing the endpoint with cURL and sending plain text or missing Content-Type; a proxy returning an HTML 502 page as the body; Postmark schema changes adding unexpected types; pointing Postmark at the wrong URL so another route's response is posted; sending test payloads with Metadata values that aren't strings.

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/3cd82cab8aeedcc1. Report an issue: GitHub.