toeverything/AFFiNE · warning · BadRequest
bad_request
bad_request
Error message
Mail delivery analytics window must be 24 or 168 hours.
What it means
BadRequest thrown by normalizeMailWindow when the analytics window hours param is present and not 24 or 168 (24*7). The mail-delivery analytics resolver only supports a 1-day or 7-day window; any other value (including 48, 72, 12) is rejected. Correctly typed as BadRequest.
Source
Thrown at packages/backend/server/src/core/mail/resolver.ts:150
}
function startOfUtcDay(value: Date) {
return new Date(
Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate())
);
}
function addUtcHours(value: Date, hours: number) {
return new Date(value.getTime() + hours * 60 * 60 * 1000);
}
function addUtcDays(value: Date, days: number) {
return new Date(value.getTime() + days * 24 * 60 * 60 * 1000);
}
function normalizeMailWindow(hours: number | undefined) {
if (hours !== undefined && hours !== 24 && hours !== 24 * 7) {
throw new BadRequest(
'Mail delivery analytics window must be 24 or 168 hours.'
);
}
const requestedHours = hours ?? 24;
const bucket = requestedHours > 24 ? ('day' as const) : ('hour' as const);
const now = new Date();
const to =
bucket === 'hour'
? addUtcHours(startOfUtcHour(now), 1)
: addUtcDays(startOfUtcDay(now), 1);
const effectiveSize =
bucket === 'hour' ? requestedHours : requestedHours / 24;
const from =
bucket === 'hour'
? addUtcHours(to, -effectiveSize)
: addUtcDays(to, -effectiveSize);
return {View on GitHub (pinned to 26c515e050)
Solutions
- Constrain the UI to two options: '24h' and '7d' (hours=24 / hours=168), or 'unset' for the default.
- If you need more windows, extend normalizeMailWindow and the bucketing logic (the bucket switches at >24) — don't just widen the guard, the bucket math assumes 24 or 168.
- Validate client-side before submitting the query.
- Return allowed values in the error message for discoverability.
Example fix
// before
if (hours !== undefined && hours !== 24 && hours !== 24 * 7) {
throw new BadRequest('Mail delivery analytics window must be 24 or 168 hours.');
}
// after — accept a typed window and document it
type MailWindow = 24 | 168;
function normalizeMailWindow(hours: MailWindow | undefined = 24) {
if (hours !== 24 && hours !== 168) {
throw new BadRequest('Mail delivery analytics window must be 24 or 168 hours.');
}
...
} Defensive patterns
Strategy: validation
Validate before calling
type MailWindow = 24 | 168;
function assertMailWindow(hours: unknown): asserts hours is MailWindow | undefined {
if (hours !== undefined && hours !== 24 && hours !== 168) {
throw new UserError('Mail window must be 24 or 168 hours');
}
} Type guard
function isValidMailWindow(hours: unknown): hours is 24 | 168 | undefined {
return hours === undefined || hours === 24 || hours === 168;
} Try / catch
if (!isValidMailWindow(hours)) {
return res.status(400).send('Use 24 or 168 hours');
}
await resolver.adminMailDeliveries({ hours }); Prevention
- Offer only '24h' and '7d' toggles in the UI.
- Type the param as a literal union so invalid values are rejected at compile time.
- If you need more windows, extend the bucket math, not just the guard.
When it happens
Trigger: Admin panel mail analytics query with hours=48, hours=12, or any value other than 24/168/undefined. Default (undefined) falls back to 24.
Common situations: A new dashboard widget offering an arbitrary hour picker instead of a fixed day/week toggle. API client built against docs that didn't note the constraint. Refactor that changed the param from a window enum to a raw number.
Related errors
- bad_request
- invalid_app_config_input
- No metadata provided
- selfhost license key is required
- Invalid pagination cursor
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/db9a9cb65b3d66cf.
Report an issue: GitHub.