knadh/listmonk · warning

unsupported bounce type: %v

Error message

unsupported bounce type: %v

What it means

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.

Source

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

	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
	}

	if !supportedBounceType {
		return nil, fmt.Errorf("unsupported bounce type: %v", n.Type)
	}

	// Look for the campaign ID in headers.
	campUUID := ""
	if v, ok := n.Metadata["X-Listmonk-Campaign"]; ok {
		campUUID = v
	}

	return []models.Bounce{{
		Email:        strings.ToLower(n.Email),
		CampaignUUID: campUUID,
		Type:         typ,
		Source:       "postmark",
		Meta:         json.RawMessage(b),
		CreatedAt:    n.BouncedAt,
	}}, nil
}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Check the logged n.Type value and compare against the supported list in internal/bounce/webhooks/postmark.go
  2. Restrict the Postmark webhook to the bounce/spam events listmonk supports
  3. Update the switch statement to map the new Postmark Type to hard/soft/complaint and rebuild
  4. If the type is informational only, filter it out at the Postmark webhook configuration level

Example fix

// before (postmark.go)
case "SpamComplaint":
    typ = models.BounceTypeComplaint
default:
    supportedBounceType = false
// after
case "SpamComplaint":
    typ = models.BounceTypeComplaint
case "NewPostmarkType":
    typ = models.BounceTypeSoft
default:
    supportedBounceType = false
Defensive patterns

Strategy: type-guard

Validate before calling

var supportedPostmarkTypes = map[string]bool{
    "HardBounce": true, "BadEmailAddress": true, "ManuallyDeactivated": true,
    "SoftBounce": true, "Transient": true, "DnsError": true,
    "SpamNotification": true, "VirusNotification": true,
    "DMARCPolicy": true, "SpamComplaint": true,
}
func isSupportedPostmarkType(t string) bool { return supportedPostmarkTypes[t] }
// inspect n.Type before/after calling ProcessBounce and skip unsupported events

Type guard

func isPostmarkBounceRecord(n json.RawMessage) (isBounce, supported bool) {
    var probe struct {
        RecordType string `json:"RecordType"`
        Type       string `json:"Type"`
    }
    if json.Unmarshal(n, &probe) != nil { return false, false }
    return probe.RecordType == "Bounce" || probe.RecordType == "SpamComplaint",
        supportedPostmarkTypes[probe.Type]
}

Try / catch

bounces, err := handler.ProcessBounce(body, c)
if err != nil {
    var unsupported string
    if _, e := fmt.Sscanf(err.Error(), "unsupported bounce type: %s", &unsupported); e == nil {
        c.Logger().Warnf("ignoring unsupported postmark type: %s", unsupported)
        return c.NoContent(http.StatusOK) // acknowledge so Postmark stops retrying
    }
    return err
}

Prevention

When it happens

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

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

Related errors


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