knadh/listmonk · error

campaigns.fieldInvalidSubject

Error message

campaigns.fieldInvalidSubject

What it means

validateCampaignFields checks strHasLen(c.Subject, 1, 5000). The subject must be non-empty and can hold up to 5000 characters because Go template logic ({{ ... }}) is allowed inside it. This error means the subject is missing or exceeds that limit.

Source

Thrown at cmd/campaigns.go:695

}

// validateCampaignFields validates incoming campaign field values.
func (a *App) validateCampaignFields(c campReq) (campReq, error) {
	if c.FromEmail == "" {
		c.FromEmail = a.cfg.FromEmail
	} else if !reFromAddress.Match([]byte(c.FromEmail)) {
		if _, err := a.importer.SanitizeEmail(c.FromEmail); err != nil {
			return c, errors.New(a.i18n.T("campaigns.fieldInvalidFromEmail"))
		}
	}

	if !strHasLen(c.Name, 1, stdInputMaxLen) {
		return c, errors.New(a.i18n.T("campaigns.fieldInvalidName"))
	}

	// Larger char limit for subject as it can contain {{ go templating }} logic.
	if !strHasLen(c.Subject, 1, 5000) {
		return c, errors.New(a.i18n.T("campaigns.fieldInvalidSubject"))
	}

	// If no content-type is specified, default to richtext.
	if c.ContentType != models.CampaignContentTypeRichtext &&
		c.ContentType != models.CampaignContentTypeHTML &&
		c.ContentType != models.CampaignContentTypePlain &&
		c.ContentType != models.CampaignContentTypeVisual &&
		c.ContentType != models.CampaignContentTypeMarkdown {
		c.ContentType = models.CampaignContentTypeRichtext
	}

	if c.ContentType != models.CampaignContentTypeVisual {
		c.BodySource.Valid = false
	}

	// If there's a "send_at" date, it should be in the future.
	if c.SendAt.Valid {
		if c.SendAt.Time.Before(time.Now()) {

View on GitHub (pinned to 670c01717d)

Solutions

  1. Set a non-empty subject in the campaign request payload.
  2. Keep template logic in the body/template and use a short subject with small placeholders like {{ .Subscriber.Name }}.
  3. Pre-measure subject length client-side (limit 5000) before submitting.

Example fix

// before
{"subject": ""}
// after
{"subject": "Your weekly digest, {{ .Subscriber.FirstName }}"}
Defensive patterns

Strategy: validation

Validate before calling

function validateSubject(subject) {
  if (typeof subject !== 'string' || subject.trim().length === 0 || subject.length > 5000) {
    throw new Error('subject must be 1-5000 characters (template logic included)');
  }
}

Type guard

function hasValidSubject(v: unknown): v is string {
  return typeof v === 'string' && v.length >= 1 && v.length <= 5000;
}

Try / catch

try {
  await api.createCampaign({ subject, ...rest });
} catch (e) {
  if (e.message.includes('fieldInvalidSubject')) {
    throw new Error('Subject is required and must be at most 5000 characters');
  }
  throw e;
}

Prevention

When it happens

Trigger: CreateCampaign/UpdateCampaign/TestCampaign with an empty 'subject' field, or a subject over 5000 chars — e.g. a subject embedding very large template blocks or long dynamic text.

Common situations: Programmatic campaign creation where the subject is optional in the caller's schema but required here; subjects with big conditional template snippets blowing past 5000 chars.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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