toeverything/AFFiNE · warning · BadRequest
bad_request
bad_request
Error message
Mail delivery analytics window must be 24 or 168 hours.
What it means
normalizeMailWindow validates the hours argument of the admin mail-delivery analytics query: only undefined (defaults to 24), 24, or 168 are accepted; anything else throws BadRequest. The value also selects the bucket: 24h→hourly buckets, 168h→daily buckets.
Solutions
- Pass exactly 24, 168, or omit the argument
- Fix the client to offer only the two supported windows (24h / 7d) instead of a free-form range picker
- Validate/coerce the input to a number before sending the GraphQL request
- If you need other windows, that requires a backend change — do not try to work around per-request
Example fix
// before
adminMailDeliveries({ input: { hours: windowDays * 24 } });
// after
const hours = windowDays === 7 ? 168 : 24; // only 24 and 168 are valid
adminMailDeliveries({ input: { hours } }); Defensive patterns
Strategy: validation
Validate before calling
const hours = input?.hours;
if (hours !== undefined && hours !== 24 && hours !== 168) {
throw new Error('hours must be 24, 168, or omitted');
}
await adminMailDeliveries({ input: { hours } }); Type guard
function isInvalidMailWindow(e: unknown): boolean {
const err = e as { extensions?: { code?: string }; message?: string };
return err.extensions?.code === 'bad_request' && /24 or 168/.test(err.message ?? '');
} Try / catch
try {
const stats = await adminMailDeliveries({ input });
} catch (e) {
if (isInvalidMailWindow(e)) return loadDefault24h(); // fall back to the default window
throw e;
} Prevention
- Offer only 'last 24h' and 'last 7d' presets in admin dashboards
- Coerce and range-check the hours argument client-side before the GraphQL call
- Keep a shared constants module for the supported analytics windows
When it happens
Trigger: Querying adminMailDeliveries with hours=48, 12, or 720; a client sending a string like '24' or an interval like '7d' where a number is required; a UI slider that allows arbitrary day ranges instead of the two fixed windows.
Common situations: Custom admin dashboards built on the GraphQL API assuming any window is supported; refactors that started computing hours client-side (e.g. diff between two dates) instead of using the two presets.
Understand the failure class
Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.
Related errors
- bad_request
- cannot_delete_own_account
- query_too_long
- workspace_id_required_to_update_team_subscription
- -32001
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/db9a9cb65b3d66cf.
Report an issue: GitHub.
Appendix: 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 b4c8548c09)