knadh/listmonk · error

subscribers.invalidJSON

Error message

subscribers.invalidJSON

What it means

When campaign attributes (c.Attribs) are provided, validateCampaignFields probes serializability with json.Marshal(c.Attribs); failure returns subscribers.invalidJSON. It ensures the attributes blob is valid JSON-shaped data before it is stored, reusing the subscriber-side i18n key.

Source

Thrown at cmd/campaigns.go:743

			c.Messenger = "email"
		} else {
			return c, errors.New(a.i18n.Ts("campaigns.fieldInvalidMessenger", "name", c.Messenger))
		}
	}

	camp := models.Campaign{Body: c.Body, TemplateBody: tplTag}
	if err := c.CompileTemplate(a.manager.TemplateFuncs(&camp)); err != nil {
		return c, errors.New(a.i18n.Ts("campaigns.fieldInvalidBody", "error", err.Error()))
	}

	if len(c.Headers) == 0 {
		c.Headers = make([]map[string]string, 0)
	}

	// Validate and initialize attribs.
	if c.Attribs != nil {
		if _, err := json.Marshal(c.Attribs); err != nil {
			return c, errors.New(a.i18n.T("subscribers.invalidJSON"))
		}
	}

	if len(c.ArchiveMeta) == 0 {
		c.ArchiveMeta = json.RawMessage("{}")
	}

	if c.ArchiveSlug.String != "" {
		// Format the slug to be alpha-numeric-dash.
		s := strings.ToLower(c.ArchiveSlug.String)
		s = strings.TrimSpace(reSlug.ReplaceAllString(s, " "))
		s = regexpSpaces.ReplaceAllString(s, "-")

		c.ArchiveSlug = null.NewString(s, true)
	} else {
		// If there's no slug set, set it to NULL in the DB.
		c.ArchiveSlug.Valid = false
	}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Send attribs as a proper JSON object (e.g. {"key": "value"}) not a string.
  2. Sanitize values client-side: JSON.stringify then re-parse to guarantee valid JSON before the API call.
  3. Remove non-UTF8 characters and non-finite numbers from the payload.
  4. Omit attribs entirely (null) if not needed, since the check only runs when Attribs != nil.

Example fix

// before
{"attribs": "{\"plan\": pro}"}
// after
{"attribs": {"plan": "pro"}}
Defensive patterns

Strategy: validation

Validate before calling

// round-trip attribs through JSON to guarantee serializability
function sanitizeAttribs(attribs) {
  if (attribs == null) return null; // omit the field entirely
  return JSON.parse(JSON.stringify(attribs));
}

Type guard

function isJSONObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v) &&
    JSON.stringify(v) !== undefined;
}

Try / catch

try {
  await api.createCampaign({ attribs, ...rest });
} catch (e) {
  if (e.message.includes('invalidJSON')) {
    throw new Error('attribs could not be serialized as JSON — check for invalid values');
  }
  throw e;
}

Prevention

When it happens

Trigger: CreateCampaign/UpdateCampaign/TestCampaign where the attribs field cannot be marshalled — non-UTF8 bytes, values of unsupported types injected via a custom client, or cyclic/odd payloads from programmatic clients.

Common situations: Clients sending attribs as a raw string instead of a JSON object, NaN/Infinity numbers, or binary data smuggled into the JSON payload by an upstream serializer.

Related errors


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