{"record":{"id":"689042a10ac3f2a2","repo":"TryGhost/Ghost","slug":"email-is-not-valid","errorCode":null,"errorMessage":"Email is not valid","messagePattern":"Email is not valid","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"ghost/core/core/server/services/lib/magic-link/magic-link.js","lineNumber":76,"sourceCode":"        this.labsService = options.labsService || undefined;\n    }\n\n    /**\n     * sendMagicLink\n     *\n     * @param {object} options\n     * @param {string} options.email - The email to send magic link to\n     * @param {TokenData} options.tokenData - The data for token\n     * @param {string} [options.type='signin'] - The type to be passed to the url and content generator functions\n     * @param {string} [options.referrer=null] - The referrer of the request, if exists. The member will be redirected back to this URL after signin.\n     * @param {boolean} [options.includeOTC=false] - Whether to send a one-time-code in the email.\n     * @returns {Promise<{token: Token, otcRef: string | null, info: SentMessageInfo}>}\n     */\n    async sendMagicLink(options) {\n        this.sentry?.captureMessage?.(`[Magic Link] Generating magic link`, {extra: options});\n\n        if (!isEmail(options.email)) {\n            throw new BadRequestError({\n                message: tpl(messages.invalidEmail)\n            });\n        }\n\n        const token = await this.tokenProvider.create(options.tokenData);\n\n        const type = options.type || 'signin';\n\n        const url = this.getSigninURL(token, type, options.referrer);\n\n        let otc = null;\n        if (options.includeOTC) {\n            try {\n                otc = await this.getOTCFromToken(token);\n            } catch (err) {\n                this.sentry?.captureException?.(err);\n                otc = null;\n            }","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/ghost/core/core/server/services/lib/magic-link/magic-link.js#L58-L94","documentation":"A BadRequestError from `MagicLinkService.sendMagicLink` when `isEmail(options.email)` returns false before any token is created or mail sent. It is the first guard in the magic-link/OTC send flow, so an invalid recipient short-circuits immediately. The check uses the `isEmail` validator, not a custom regex.","triggerScenarios":"Calling `sendMagicLink({email, tokenData, ...})` with an email that is missing, not a string, or fails the `isEmail` RFC check. Also when the field name differs (e.g. `address`) so `options.email` is undefined, or when whitespace/format issues trip the validator.","commonSituations":"A member types a malformed address in the portal sign-in form; the portal sends an empty string because the input was uncontrolled; an integration passes the member's name field instead of email; copy-paste introduces a trailing space or newline.","solutions":["Validate and trim the email with the same `isEmail` check on the client before calling `sendMagicLink`.","Confirm you pass the address under the `email` key (not `address`/`to`).","Strip whitespace/newlines: `email.trim()`.","If the member's stored email is invalid, prompt correction in the portal before initiating the magic link."],"exampleFix":"// before\nawait magicLink.sendMagicLink({email: input, tokenData}); // input may be invalid\n\n// after\nconst email = String(input ?? '').trim();\nif (!isEmail(email)) throw new Error('Enter a valid email');\nawait magicLink.sendMagicLink({email, tokenData});","handlingStrategy":"validation","validationCode":"const {isEmail} = require('@tryghost/validator');\nfunction validateMagicLinkEmail(email) {\n  const trimmed = String(email ?? '').trim();\n  if (!isEmail(trimmed)) throw new Error('Email is not valid');\n  return trimmed;\n}","typeGuard":"const isValidEmail = (v) => typeof v === 'string' && isEmail(v.trim());","tryCatchPattern":"try {\n  await magicLink.sendMagicLink({email, tokenData});\n} catch (err) {\n  if (err.type === 'BadRequestError' && /not valid/i.test(err.message)) showEmailError(err.message);\n  else throw err;\n}","preventionTips":["Trim and isEmail-check the address before calling sendMagicLink.","Pass the address under the `email` option key.","Disable the submit button until a valid email is entered."],"tags":["magic-link","validation","email","auth","portal"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}