{"record":{"id":"d19e819ceb166c47","repo":"koala73/worldmonitor","slug":"backfillcanary250-resend-list-contacts-res-sta","errorCode":null,"errorMessage":"[backfillCanary250] Resend list-contacts ${res.status}: ${body}","messagePattern":"\\[backfillCanary250\\] Resend list-contacts (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/backfillCanaryWaveStamps.ts","lineNumber":171,"sourceCode":"      // documented inconsistently across Resend's docs pages — only\n      // the `/segments/{id}/contacts` route is canonical.\n      const url = new URL(\n        `${RESEND_API_BASE}/segments/${encodeURIComponent(CANARY_SEGMENT_ID)}/contacts`,\n      );\n      url.searchParams.set(\"limit\", String(RESEND_PAGE_SIZE));\n      if (after) url.searchParams.set(\"after\", after);\n\n      const res = await fetch(url.toString(), {\n        method: \"GET\",\n        headers: {\n          Authorization: `Bearer ${apiKey}`,\n          \"User-Agent\": USER_AGENT,\n        },\n      });\n\n      if (!res.ok) {\n        const body = await res.text().catch(() => \"<no body>\");\n        throw new Error(\n          `[backfillCanary250] Resend list-contacts ${res.status}: ${body}`,\n        );\n      }\n\n      const json = (await res.json()) as ResendListContactsResponse;\n      if (!json || !Array.isArray(json.data)) {\n        throw new Error(\n          `[backfillCanary250] unexpected Resend response shape: ${JSON.stringify(json).slice(0, 200)}`,\n        );\n      }\n\n      for (const contact of json.data) {\n        stats.fetched++;\n        const normalizedEmail = (contact.email ?? \"\").trim().toLowerCase();\n        if (!normalizedEmail) {\n          stats.failed++;\n          continue;\n        }","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/backfillCanaryWaveStamps.ts#L153-L189","documentation":"Thrown by backfillCanary250 when the Resend GET /segments/{id}/contacts request returns a non-OK HTTP status. The error includes the status code and the response body (or '<no body>' if reading the body failed) so the operator can diagnose the upstream Resend failure. This wraps any 4xx/5xx from Resend into an actionable error.","triggerScenarios":"Resend returns 401 (invalid/expired API key), 403 (forbidden), 404 (segment id does not exist), 429 (rate limit), or 5xx (Resend outage); the CANARY_SEGMENT_ID points to a deleted segment; network/proxy interference altered the response.","commonSituations":"RESEND_API_KEY was revoked or rotated but not updated in Convex env; the canary segment was deleted in the Resend dashboard; hitting Resend's rate limit during a large backfill; transient Resend 5xx during the request window.","solutions":["Check the HTTP status in the error message: 401/403 → rotate and redeploy RESEND_API_KEY; 404 → verify CANARY_SEGMENT_ID exists in Resend; 429 → back off and retry; 5xx → retry after Resend recovers.","Confirm RESEND_API_KEY is valid and authorized for the Resend account owning the canary segment.","Verify CANARY_SEGMENT_ID in the source matches an existing Resend segment.","For rate limits, reduce RESEND_PAGE_SIZE or add delays between pages."],"exampleFix":"// before\nconst res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` }});\nif (!res.ok) throw new Error(`...${res.status}...`); // 401 from expired key\n// after — ensure key is current\n// $ npx convex env set RESEND_API_KEY re_newvalue\n// then re-run backfillCanary250","handlingStrategy":"retry","validationCode":"if (!process.env.RESEND_API_KEY) throw new Error(\"RESEND_API_KEY missing\");\n// Preflight: verify segment exists with a HEAD/GET before paging\n// (optional — reduces surprise failures)","typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    await backfillCanary250(ctx, {});\n    break;\n  } catch (e) {\n    const status = /Resend list-contacts (\\d+)/.exec(e.message)?.[1];\n    if (status === \"429\" || (status && +status >= 500) && attempt < 2) {\n      await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["Keep RESEND_API_KEY current; rotate before expiry.","Verify CANARY_SEGMENT_ID exists in the Resend dashboard before backfill.","For 429/5xx, retry with backoff; for 401/403/404, fix config first."],"tags":["convex","broadcast","resend","http","network","backfill"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}