{"record":{"id":"f976e55e30275c00","repo":"TryGhost/Ghost","slug":"token-is-required","errorCode":null,"errorMessage":"Token is required","messagePattern":"Token is required","errorType":"validation","errorClass":"ValidationError","httpStatus":422,"severity":"error","filePath":"ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js","lineNumber":119,"sourceCode":"module.exports = {\n    async add(apiConfig, frame) {\n        await validateAutomatedEmail(frame);\n    },\n    async edit(apiConfig, frame) {\n        await validateAutomatedEmail(frame);\n    },\n    editSenders(apiConfig, frame) {\n        const senderName = frame.data.sender_name;\n        const senderEmail = frame.data.sender_email;\n        const senderReplyTo = frame.data.sender_reply_to;\n\n        validateOptionalStringField(senderName, 'Sender name must be a string');\n        validateOptionalStringField(senderEmail, 'Sender email must be a string');\n        validateOptionalStringField(senderReplyTo, 'Reply-to email must be a string');\n    },\n    verifySenderUpdate(apiConfig, frame) {\n        if (typeof frame.data.token !== 'string' || !frame.data.token.trim()) {\n            throw new ValidationError({\n                message: tpl(messages.tokenRequired)\n            });\n        }\n    },\n    preview(apiConfig, frame) {\n        validatePreviewData(frame);\n    },\n    sendTestEmail(apiConfig, frame) {\n        const email = frame.data.email;\n\n        if (typeof email !== 'string' || !validator.isEmail(email)) {\n            throw new ValidationError({\n                message: tpl(messages.invalidEmailReceived)\n            });\n        }\n\n        validatePreviewData(frame);\n    }","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/ghost/core/core/server/api/endpoints/utils/validators/input/automated_emails.js#L101-L137","documentation":"Thrown by the `verifySenderUpdate` validator when `frame.data.token` is not a non-empty trimmed string. The sender-update verification flow requires the token from the confirmation email; without it Ghost cannot verify the sender change and returns a 422 ValidationError.","triggerScenarios":"A request to the verify-sender-update endpoint omits `token`, sends it as a non-string type, or sends whitespace only.","commonSituations":"User clicked a verification link whose token query param was stripped; frontend forwarded the request without extracting the token from the URL; token expired/cleared; payload key misspelled.","solutions":["Extract the `token` query parameter from the verification email/link and include it in the request body.","Confirm the token is a non-empty string before submitting.","Re-request the sender update email if the token was lost."],"exampleFix":"// before\nawait api.email.verifySenderUpdate({data: {}}); // no token\n\n// after\nawait api.email.verifySenderUpdate({data: {token: urlSearchParams.get('token')}});","handlingStrategy":"validation","validationCode":"function isNonEmptyToken(v: unknown): v is string {\n    return typeof v === 'string' && v.trim().length > 0;\n}\nif (!isNonEmptyToken(payload.token)) {\n    throw new Error('token is required');\n}","typeGuard":"function isNonEmptyString(v: unknown): v is string {\n    return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Extract the `token` from the verification email link before calling the endpoint.","Confirm the token has not been stripped from the URL/payload.","Re-request the sender-update email if the token is lost."],"tags":["api","validation","email","automated-emails","sender-verification","token"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}