{"record":{"id":"161e8498a5277490","repo":"koala73/worldmonitor","slug":"email-required-for-email-channel","errorCode":null,"errorMessage":"email required for email channel","messagePattern":"email required for email channel","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":509,"sourceCode":"\n    if (args.channelType === \"telegram\") {\n      if (!args.chatId) throw new ConvexError(\"chatId required for telegram channel\");\n      const doc = { userId, channelType: \"telegram\" as const, chatId: args.chatId, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"slack\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for slack channel\");\n      const doc = { userId, channelType: \"slack\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"email\") {\n      if (!args.email) throw new ConvexError(\"email required for email channel\");\n      const doc = { userId, channelType: \"email\" as const, email: args.email, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"webhook\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for webhook channel\");\n      const doc = { userId, channelType: \"webhook\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else {\n      throw new ConvexError(\"discord channel must be set via set-discord-oauth\");\n    }\n  },","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/notificationChannels.ts#L491-L527","documentation":"Thrown by the `setChannel` Convex mutation (convex/notificationChannels.ts) when `args.channelType === \"email\"` but `args.email` is empty or undefined. The handler must persist a channel document with an `email` field, so it rejects the call before any DB write. It is a plain string-typed ConvexError, so the client receives the literal message in `err.data` (not `err.data.kind`).","triggerScenarios":"Calling `api.notificationChannels.setChannel` with `{ channelType: \"email\" }` while omitting the `email` argument or passing `email: \"\"`. Any code path that builds the args object conditionally and leaves `email` falsy when the email branch is selected.","commonSituations":"A notification-settings form whose submit button is enabled before the email input is filled; a refactor that renamed the email field on the form but not in the mutation args; copy-pasting a slack/webhook call and forgetting to swap in the `email` field.","solutions":["Pass a non-empty `email` string: `setChannel({ channelType: \"email\", email })`.","Keep the submit button disabled until the email field is non-empty and passes a basic format check.","Verify the form wires the input value into the `email` arg key exactly (not `webhookEnvelope` or `chatId`)."],"exampleFix":"// before\nawait setChannel({ channelType: \"email\" });\n\n// after\nawait setChannel({ channelType: \"email\", email: trimmedEmail });","handlingStrategy":"validation","validationCode":"const email = (formEmail ?? \"\").trim();\nif (channelType === \"email\" && !email) {\n  setFieldError(\"email\", \"Email is required\");\n  return;\n}\nawait setChannel({ channelType: \"email\", email });","typeGuard":"function hasEmailArg(a: { email?: string }): a is { email: string } {\n  return typeof a.email === \"string\" && a.email.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Disable the email-channel submit button until a non-empty email is entered.","Run the same trim+non-empty check client-side that the server runs."],"tags":["validation","convex","notifications","email"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}