koala73/worldmonitor · error · ConvexError

email required for email channel

Error message

email required for email channel

What it means

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`).

Source

Thrown at convex/notificationChannels.ts:509

    if (args.channelType === "telegram") {
      if (!args.chatId) throw new ConvexError("chatId required for telegram channel");
      const doc = { userId, channelType: "telegram" as const, chatId: args.chatId, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "slack") {
      if (!args.webhookEnvelope) throw new ConvexError("webhookEnvelope required for slack channel");
      const doc = { userId, channelType: "slack" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "email") {
      if (!args.email) throw new ConvexError("email required for email channel");
      const doc = { userId, channelType: "email" as const, email: args.email, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "webhook") {
      if (!args.webhookEnvelope) throw new ConvexError("webhookEnvelope required for webhook channel");
      const doc = { userId, channelType: "webhook" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else {
      throw new ConvexError("discord channel must be set via set-discord-oauth");
    }
  },

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Pass a non-empty `email` string: `setChannel({ channelType: "email", email })`.
  2. Keep the submit button disabled until the email field is non-empty and passes a basic format check.
  3. Verify the form wires the input value into the `email` arg key exactly (not `webhookEnvelope` or `chatId`).

Example fix

// before
await setChannel({ channelType: "email" });

// after
await setChannel({ channelType: "email", email: trimmedEmail });
Defensive patterns

Strategy: validation

Validate before calling

const email = (formEmail ?? "").trim();
if (channelType === "email" && !email) {
  setFieldError("email", "Email is required");
  return;
}
await setChannel({ channelType: "email", email });

Type guard

function hasEmailArg(a: { email?: string }): a is { email: string } {
  return typeof a.email === "string" && a.email.trim().length > 0;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/161e8498a5277490. Report an issue: GitHub.