koala73/worldmonitor · error · Error

[backfillCanary250] unexpected Resend response shape: ${JSON

Error message

[backfillCanary250] unexpected Resend response shape: ${JSON.stringify(json).slice(0, 200)}

What it means

Thrown by backfillCanary250 when the Resend response parses as JSON but does not have the expected shape — specifically json is falsy or json.data is not an array. This guards against silent schema drift where Resend changes its API response format, returning a 200 but with a different structure that would break the pagination/stamping loop.

Source

Thrown at convex/broadcast/backfillCanaryWaveStamps.ts:178

      const res = await fetch(url.toString(), {
        method: "GET",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "User-Agent": USER_AGENT,
        },
      });

      if (!res.ok) {
        const body = await res.text().catch(() => "<no body>");
        throw new Error(
          `[backfillCanary250] Resend list-contacts ${res.status}: ${body}`,
        );
      }

      const json = (await res.json()) as ResendListContactsResponse;
      if (!json || !Array.isArray(json.data)) {
        throw new Error(
          `[backfillCanary250] unexpected Resend response shape: ${JSON.stringify(json).slice(0, 200)}`,
        );
      }

      for (const contact of json.data) {
        stats.fetched++;
        const normalizedEmail = (contact.email ?? "").trim().toLowerCase();
        if (!normalizedEmail) {
          stats.failed++;
          continue;
        }
        try {
          const out = await ctx.runMutation(
            internal.broadcast.backfillCanaryWaveStamps
              ._stampWaveByNormalizedEmail,
            {
              normalizedEmail,
              waveLabel: CANARY_WAVE_LABEL,

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Inspect the JSON fragment in the error message to see what Resend actually returned.
  2. Check Resend's API changelog for response-shape changes and update the ResendListContactsResponse type/parsing accordingly.
  3. If a proxy is interfering, route the request outside the proxy or whitelist the Resend domain.
  4. Log the full response once to confirm whether it's a transient anomaly or a permanent schema change.

Example fix

// before
const json = (await res.json()) as ResendListContactsResponse;
if (!json || !Array.isArray(json.data)) throw new Error("unexpected shape");
// after — log full body for diagnosis, then adapt parser
const raw = await res.text();
console.log("[backfill] raw Resend body:", raw.slice(0, 500));
const json = JSON.parse(raw);
const contacts = Array.isArray(json) ? json : json.data;
if (!Array.isArray(contacts)) throw new Error("unsupported shape");
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate shape after parsing
const json = await res.json();
if (!json || !Array.isArray(json.data)) {
  throw new Error("Resend response schema drifted — investigate before continuing");
}

Type guard

function isResendListContactsResponse(json: unknown): json is { data: unknown[] } {
  return !!json && typeof json === "object" && Array.isArray((json as any).data);
}

Try / catch

try {
  await backfillCanary250(ctx, {});
} catch (e) {
  if (e.message.includes("unexpected Resend response shape")) {
    // log full body, check Resend changelog, then decide
    console.error(e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Resend returns a 200 OK but with a body where `data` is missing, is an object instead of an array, or the response is an error envelope that still returns 200; Resend ships a breaking API change; an unexpected proxy/CDN injects a non-JSON body that happened to parse.

Common situations: Resend API version bump changed the response envelope; a man-in-the-middle (corporate proxy) returned an HTML interstitial that parsed as partial JSON; Resend returned a top-level error object instead of a data array.

Related errors


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