{"record":{"id":"b882328fb734144e","repo":"knadh/listmonk","slug":"unsupported-bounce-type-v","errorCode":null,"errorMessage":"unsupported bounce type: %v","messagePattern":"unsupported bounce type: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/bounce/webhooks/postmark.go","lineNumber":83,"sourceCode":"\tif n.RecordType != \"Bounce\" && n.RecordType != \"SpamComplaint\" {\n\t\treturn nil, nil\n\t}\n\n\tsupportedBounceType := true\n\ttyp := models.BounceTypeHard\n\tswitch n.Type {\n\tcase \"HardBounce\", \"BadEmailAddress\", \"ManuallyDeactivated\":\n\t\ttyp = models.BounceTypeHard\n\tcase \"SoftBounce\", \"Transient\", \"DnsError\", \"SpamNotification\", \"VirusNotification\", \"DMARCPolicy\":\n\t\ttyp = models.BounceTypeSoft\n\tcase \"SpamComplaint\":\n\t\ttyp = models.BounceTypeComplaint\n\tdefault:\n\t\tsupportedBounceType = false\n\t}\n\n\tif !supportedBounceType {\n\t\treturn nil, fmt.Errorf(\"unsupported bounce type: %v\", n.Type)\n\t}\n\n\t// Look for the campaign ID in headers.\n\tcampUUID := \"\"\n\tif v, ok := n.Metadata[\"X-Listmonk-Campaign\"]; ok {\n\t\tcampUUID = v\n\t}\n\n\treturn []models.Bounce{{\n\t\tEmail:        strings.ToLower(n.Email),\n\t\tCampaignUUID: campUUID,\n\t\tType:         typ,\n\t\tSource:       \"postmark\",\n\t\tMeta:         json.RawMessage(b),\n\t\tCreatedAt:    n.BouncedAt,\n\t}}, nil\n}\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/bounce/webhooks/postmark.go#L65-L101","documentation":"Postmark bounce notifications carry a Type field classifying the bounce. ProcessBounce only maps a fixed set of Postmark bounce types (HardBounce, BadEmailAddress, ManuallyDeactivated, SoftBounce, Transient, DnsError, SpamNotification, VirusNotification, DMARCPolicy, SpamComplaint); anything else returns 'unsupported bounce type'. This prevents unmapped Postmark events from being recorded with a wrong bounce class.","triggerScenarios":"A Postmark webhook fires with RecordType 'Bounce' or 'SpamComplaint' but a Type value outside the supported switch list, e.g. 'AntiSpam'/'Unknown' types, new Postmark bounce types introduced after this code was written, or a SpamComplaint RecordType carrying an unexpected Type value.","commonSituations":"Postmark adds a new bounce Type (product update) not yet handled; enabling Postmark message streams that emit types listmonk doesn't map; test events from Postmark's webhook tester with exotic types; misconfigured webhook subscribed to all events instead of bounce events.","solutions":["Check the logged n.Type value and compare against the supported list in internal/bounce/webhooks/postmark.go","Restrict the Postmark webhook to the bounce/spam events listmonk supports","Update the switch statement to map the new Postmark Type to hard/soft/complaint and rebuild","If the type is informational only, filter it out at the Postmark webhook configuration level"],"exampleFix":"// before (postmark.go)\ncase \"SpamComplaint\":\n    typ = models.BounceTypeComplaint\ndefault:\n    supportedBounceType = false\n// after\ncase \"SpamComplaint\":\n    typ = models.BounceTypeComplaint\ncase \"NewPostmarkType\":\n    typ = models.BounceTypeSoft\ndefault:\n    supportedBounceType = false","handlingStrategy":"type-guard","validationCode":"var supportedPostmarkTypes = map[string]bool{\n    \"HardBounce\": true, \"BadEmailAddress\": true, \"ManuallyDeactivated\": true,\n    \"SoftBounce\": true, \"Transient\": true, \"DnsError\": true,\n    \"SpamNotification\": true, \"VirusNotification\": true,\n    \"DMARCPolicy\": true, \"SpamComplaint\": true,\n}\nfunc isSupportedPostmarkType(t string) bool { return supportedPostmarkTypes[t] }\n// inspect n.Type before/after calling ProcessBounce and skip unsupported events","typeGuard":"func isPostmarkBounceRecord(n json.RawMessage) (isBounce, supported bool) {\n    var probe struct {\n        RecordType string `json:\"RecordType\"`\n        Type       string `json:\"Type\"`\n    }\n    if json.Unmarshal(n, &probe) != nil { return false, false }\n    return probe.RecordType == \"Bounce\" || probe.RecordType == \"SpamComplaint\",\n        supportedPostmarkTypes[probe.Type]\n}","tryCatchPattern":"bounces, err := handler.ProcessBounce(body, c)\nif err != nil {\n    var unsupported string\n    if _, e := fmt.Sscanf(err.Error(), \"unsupported bounce type: %s\", &unsupported); e == nil {\n        c.Logger().Warnf(\"ignoring unsupported postmark type: %s\", unsupported)\n        return c.NoContent(http.StatusOK) // acknowledge so Postmark stops retrying\n    }\n    return err\n}","preventionTips":["Track Postmark docs for new bounce Types and update the switch when they appear","Configure the Postmark webhook to send only supported events","Return 200 for ignorable events so Postmark does not retry indefinitely"],"tags":["webhook","postmark","bounce-type","go"],"backgroundTag":"unsupported-bounce-type","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}