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

  1. Constrain the UI to two options: '24h' and '7d' (hours=24 / hours=168), or 'unset' for the default.
  2. 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.
  3. Validate client-side before submitting the query.
  4. 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

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


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/db9a9cb65b3d66cf. Report an issue: GitHub.