koala73/worldmonitor · error · ValidationError
Invalid email address
Error message
Invalid email address
What it means
Syntactic gate on the waitlist registration (register-interest.ts:324): the email must be truthy, at most MAX_EMAIL_LENGTH=320 chars, and match /^[^\s@]+@[^^\s@]+\.[^s@]+$/. It rejects anything without exactly one @, a dot in the domain part, or containing whitespace. This runs BEFORE the deeper validateEmail() content checks (error 311).
Source
Thrown at server/worldmonitor/leads/v1/register-interest.ts:325
throw new ApiError(503, 'Rate-limit service temporarily unavailable', '');
}
if (!scoped.allowed) {
throw new ApiError(429, 'Too many requests', '');
}
} else {
const turnstileOk = await verifyTurnstile({
token: req.turnstileToken || '',
ip,
logPrefix: '[register-interest]',
});
if (!turnstileOk) {
throw new ApiError(403, 'Bot verification failed', '');
}
}
const { email, source, appVersion, referredBy } = req;
if (!email || email.length > MAX_EMAIL_LENGTH || !EMAIL_RE.test(email)) {
throw new ValidationError([{ field: 'email', description: 'Invalid email address' }]);
}
const emailCheck = await validateEmail(email);
if (!emailCheck.valid) {
throw new ValidationError([{ field: 'email', description: emailCheck.reason }]);
}
const safeSource = source ? source.slice(0, MAX_META_LENGTH) : 'unknown';
const safeAppVersion = appVersion ? appVersion.slice(0, MAX_META_LENGTH) : 'unknown';
const safeReferredBy = referredBy ? referredBy.slice(0, 20) : undefined;
const convexUrl = process.env.CONVEX_URL;
if (!convexUrl) {
throw new ApiError(503, 'Registration service unavailable', '');
}
const client = new ConvexHttpClient(convexUrl);
const result = (await client.mutation(api.registerInterest.register, {View on GitHub (pinned to a96956387a)
Solutions
- Trim input and run the same regex client-side before submit
- Use <input type="email"> plus a required check in the form
- Enforce the 320-char cap on the input's maxlength
- Only after this passes, expect deeper domain checks — fix syntax first
Example fix
// before
await registerInterest({ email: ' jane@doe' , ... }); // no dot + spaces -> ValidationError
// after
const email = rawEmail.trim();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || email.length > 320) throw new Error('fix email');
await registerInterest({ email, ... }); Defensive patterns
Strategy: validation
Validate before calling
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const email = rawInput.trim();
if (!email || email.length > 320 || !EMAIL_RE.test(email)) {
showFieldError('email', 'Enter a valid email address');
return;
}
await registerInterest({ ...req, email }); Type guard
function isSyntacticEmail(v: unknown): v is string {
return typeof v === 'string' && v.length <= 320 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
} Try / catch
try { await registerInterest(req); }
catch (e) {
if (isValidationError(e) && e.violations?.some(v => v.field === 'email')) {
showFieldError('email', e.violations.find(v => v.field === 'email')!.description);
return;
}
throw e;
} Prevention
- Trim email input and enforce maxlength=320 on the field
- Run the identical regex client-side before spending a Turnstile token
- Use type="email" + required so the browser blocks the obvious cases
When it happens
Trigger: Submitting "user@localhost" (no dot in domain), "user @mail.com" (space), "user@@mail.com", a >320-char address, undefined/null email, or untrimmed input with trailing spaces.
Common situations: Free-text input without client validation; pasting emails that carry trailing whitespace or a semicolon from an address book; test payloads with placeholder strings like "test" or "a@b".
Related errors
AI-assisted analysis of koala73/worldmonitor@a96956387a (2026-08-21).
Data as JSON: /api/errors/02598f7c82e7d84a.
Report an issue: GitHub.