Notification API Endpoint Pinning (SCR-06)
Notification API Endpoint Pinning (SCR-06)
As of v1.0.480, the API base URLs used by the email, SMS, and WhatsApp notification modules are configurable via environment variables. This implements supply-chain risk control SCR-06.
Why this exists
Previously, the Resend and Twilio API URLs were hardcoded in the notification modules. Extracting them to environment variables enables three operational capabilities without requiring a code change or deployment:
- Mock / local testing — point to a local mock server (e.g.
http://localhost:8080) during development or integration testing. - Regional failover — redirect traffic to a geographically closer or higher-availability endpoint.
- Emergency rerouting — switch delivery providers at runtime if an upstream provider has an outage.
Environment variables
RESEND_API_URL
| Property | Value |
|---|---|
| Required | No |
| Default | https://api.resend.com/emails |
| Module | src/lib/notifications/email.ts |
Overrides the endpoint used when sending transactional emails via Resend.
# .env.local — point to a local mock during development
RESEND_API_URL=http://localhost:8025/emails
TWILIO_API_BASE
| Property | Value |
|---|---|
| Required | No |
| Default | https://api.twilio.com |
| Modules | src/lib/notifications/sms.ts, src/lib/notifications/whatsapp.ts |
Overrides the Twilio REST API base URL. This single variable controls both SMS and WhatsApp message delivery since both use the Twilio Messages API.
# .env.local — point to a Twilio region-specific host
TWILIO_API_BASE=https://api.twilio.com
Behaviour when unset
If either variable is not present in the environment, the module falls back to its production default (the previously hardcoded value). There is no change in behaviour for existing deployments.
Implementation detail
Each module exposes a private helper (getResendApiUrl() / getTwilioApiBase()) that is called once per request — not at module load time. This means the env var is read fresh on every invocation, so updates to the variable in a long-running process take effect without a restart.
// email.ts
const DEFAULT_RESEND_API_URL = "https://api.resend.com/emails";
function getResendApiUrl(): string {
return process.env.RESEND_API_URL || DEFAULT_RESEND_API_URL;
}
// sms.ts & whatsapp.ts
const DEFAULT_TWILIO_API_BASE = "https://api.twilio.com";
function getTwilioApiBase(): string {
return process.env.TWILIO_API_BASE || DEFAULT_TWILIO_API_BASE;
}
Inngest function config lint
Also introduced in this release is scripts/lint-inngest.ts, a CI script that enforces consistent Inngest function configuration across the codebase.
What it checks
The script scans every .ts / .js file in src/inngest/functions/ and verifies that any file containing a createFunction call also defines:
retries:— explicit retry countconcurrency:— explicit concurrency limit
Why it matters
Functions without concurrency limits can fan out uncontrollably under load. Functions without explicit retry counts inherit Inngest's platform default, making behaviour harder to reason about in production.
Running the lint locally
npx ts-node scripts/lint-inngest.ts
Exit code 0 = all functions compliant. Exit code 1 = one or more functions are missing required config, with specific file paths and missing fields listed in stderr.
Example output (failure)
[lint-inngest] ❌ Inngest function lint failures:
src/inngest/functions/my-function.ts — missing: concurrency
Every inngest.createFunction() call must include explicit `retries` and `concurrency` config.