{"record":{"id":"770fbe33f76c30f8","repo":"knadh/listmonk","slug":"campaigns-fieldinvalidsendat","errorCode":null,"errorMessage":"campaigns.fieldInvalidSendAt","messagePattern":"campaigns\\.fieldInvalidSendAt","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/campaigns.go","lineNumber":714,"sourceCode":"\t}\n\n\t// If no content-type is specified, default to richtext.\n\tif c.ContentType != models.CampaignContentTypeRichtext &&\n\t\tc.ContentType != models.CampaignContentTypeHTML &&\n\t\tc.ContentType != models.CampaignContentTypePlain &&\n\t\tc.ContentType != models.CampaignContentTypeVisual &&\n\t\tc.ContentType != models.CampaignContentTypeMarkdown {\n\t\tc.ContentType = models.CampaignContentTypeRichtext\n\t}\n\n\tif c.ContentType != models.CampaignContentTypeVisual {\n\t\tc.BodySource.Valid = false\n\t}\n\n\t// If there's a \"send_at\" date, it should be in the future.\n\tif c.SendAt.Valid {\n\t\tif c.SendAt.Time.Before(time.Now()) {\n\t\t\treturn c, errors.New(a.i18n.T(\"campaigns.fieldInvalidSendAt\"))\n\t\t}\n\t}\n\n\tif len(c.ListIDs) == 0 {\n\t\treturn c, errors.New(a.i18n.T(\"campaigns.fieldInvalidListIDs\"))\n\t}\n\n\tif !a.manager.HasMessenger(c.Messenger) {\n\t\t// If it's a specific SMTP, but it's no longer available (removed/disabled), fall back to general email messenger.\n\t\tif strings.HasPrefix(c.Messenger, \"email-\") {\n\t\t\tc.Messenger = \"email\"\n\t\t} else {\n\t\t\treturn c, errors.New(a.i18n.Ts(\"campaigns.fieldInvalidMessenger\", \"name\", c.Messenger))\n\t\t}\n\t}\n\n\tcamp := models.Campaign{Body: c.Body, TemplateBody: tplTag}\n\tif err := c.CompileTemplate(a.manager.TemplateFuncs(&camp)); err != nil {","sourceCodeStart":696,"sourceCodeEnd":732,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/cmd/campaigns.go#L696-L732","documentation":"When the campaign has a send_at date set (c.SendAt.Valid), validateCampaignFields requires it to be strictly in the future: if c.SendAt.Time.Before(time.Now()) it returns campaigns.fieldInvalidSendAt. The library refuses to schedule a campaign in the past.","triggerScenarios":"CreateCampaign or UpdateCampaign with a send_at timestamp earlier than the server's current time, e.g. sending a RFC3339 string generated earlier or in a timezone that resolves before server 'now'.","commonSituations":"Clock skew between client and server, client generating the timestamp in a different timezone (naive local time parsed as UTC), or retrying a stale payload minutes later so the date is now past.","solutions":["Generate send_at at the last possible moment before the request using UTC RFC3339 and verify it is > time.Now().","If you don't want scheduling, omit send_at / send null so SendAt stays invalid (unset).","Update the campaign to a new future date if the original slot has already passed.","Account for timezone: send full ISO-8601 with offset instead of naive local datetime strings."],"exampleFix":"// before\n{\"send_at\": \"2026-01-01T09:00:00Z\"}\n// after\n{\"send_at\": time.Now().Add(30 * time.Minute).UTC().Format(time.RFC3339)}","handlingStrategy":"validation","validationCode":"function assertFutureSendAt(sendAt) {\n  if (sendAt == null) return; // unset is fine\n  const t = new Date(sendAt);\n  if (isNaN(t.getTime()) || t.getTime() <= Date.now()) {\n    throw new Error('send_at must be a valid RFC3339 date in the future');\n  }\n}","typeGuard":"function isFutureDate(v: unknown): v is string {\n  if (typeof v !== 'string') return false;\n  const t = new Date(v);\n  return !isNaN(t.getTime()) && t.getTime() > Date.now();\n}","tryCatchPattern":"try {\n  await api.createCampaign({ send_at: sendAt, ...rest });\n} catch (e) {\n  if (e.message.includes('fieldInvalidSendAt')) {\n    throw new Error(`send_at ${sendAt} is in the past relative to the server clock`);\n  }\n  throw e;\n}","preventionTips":["Always send RFC3339/ISO-8601 timestamps with explicit UTC offset.","Compute send_at at request time, never cache it in long-running jobs.","Add a small safety margin (e.g. now + 1 minute) for clock skew.","Omit send_at for immediate sends."],"tags":["validation","campaign","datetime","scheduling"],"backgroundTag":"date-in-the-past","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}