From 2572e02093bad553b93a05459e30c1834c4daf41 Mon Sep 17 00:00:00 2001 From: Holger Selover-Stephan Date: Sat, 18 Jul 2026 10:13:10 -0700 Subject: [PATCH] Apply optional polish from PR #1 sidecar review The four blocking review items already landed in the merged PR #1. This addresses the remaining optional "Additional observations": - resend.ts: log a warning (status only, never the body) when a non-JSON provider response is swallowed, to aid debugging the upstream_unreachable path. - errors.ts + tsconfig.json: add `override` to the four extraFields() overrides and enable noImplicitOverride so it stays enforced. - README.md: document a manual staging smoke test against real Resend before cutting a release. Skipped the awareness-only items (idempotency-key charset is intentionally tighter than Resend's spec; existing log-leak assertion is sufficient). Typecheck clean, 31 tests pass. Co-Authored-By: Claude Opus 4.8 --- README.md | 6 ++++++ src/errors.ts | 8 ++++---- src/resend.ts | 3 +++ tsconfig.json | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aaaf213..738debf 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,9 @@ npm run dev npm test npm run typecheck ``` + +The test suite covers the Resend seam with an injected fake `fetch`, so it never +touches the network. Before cutting a release, run a manual smoke test against +real Resend in staging: point `RESEND_API_KEY` + `RESEND_FROM` (or an inline +`apiKey`/`from`) at a Resend test key and confirm `POST /ops/send-email` returns +a real `email_...` id. diff --git a/src/errors.ts b/src/errors.ts index 7310179..222690c 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -21,7 +21,7 @@ export class ValidationError extends ResendToolError { ) { super(`This request couldn't be processed: ${reason}`); } - protected extraFields() { + protected override extraFields() { return { field: this.field, reason: this.reason }; } } @@ -40,7 +40,7 @@ export class ProviderUnauthorizedError extends ResendToolError { constructor(public providerStatus: number) { super(`Resend rejected the configured credentials (HTTP ${providerStatus}).`); } - protected extraFields() { + protected override extraFields() { return { providerStatus: this.providerStatus }; } } @@ -51,7 +51,7 @@ export class ProviderRejectedError extends ResendToolError { constructor(public providerStatus: number) { super(`Resend rejected the email (HTTP ${providerStatus}).`); } - protected extraFields() { + protected override extraFields() { return { providerStatus: this.providerStatus }; } } @@ -78,7 +78,7 @@ export class UpstreamTimeoutError extends ResendToolError { constructor(public timeoutSeconds: number) { super(`The Resend request timed out after ${timeoutSeconds}s.`); } - protected extraFields() { + protected override extraFields() { return { timeoutSeconds: this.timeoutSeconds }; } } diff --git a/src/resend.ts b/src/resend.ts index 1b253b0..ae426d4 100644 --- a/src/resend.ts +++ b/src/resend.ts @@ -57,6 +57,9 @@ export async function sendEmail( payload = (await res.json()) as Record | null; } catch { if (controller.signal.aborted) throw new Error('timed out mid-body'); + // Non-JSON provider response. Downstream id-extraction maps this to + // upstream_unreachable; log the status (never the body) to aid debugging. + logger.warn('resend response body was not JSON', { status: res.status }); payload = null; } } catch (err) { diff --git a/tsconfig.json b/tsconfig.json index e4b8395..07bb206 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "sourceMap": true, "noUnusedLocals": true, "noUnusedParameters": true, + "noImplicitOverride": true, "noFallthroughCasesInSwitch": true }, "include": ["src/**/*.ts"],