Prerequisites
- A Vercel account and a project to deploy
- An AhaSend account with a verified sending domain
- An API key permitted to send from that domain, and your account ID
Install the SDK
Choose a Runtime
Use Vercel’s default server runtime; no runtime setting is required for the SDK. Vercel recommends migrating from the Edge runtime to Node.js, and Next.js 16.3 and later no longer acceptruntime = 'edge' at all. If an older route still
uses the Edge runtime, the SDK works there too using the runtime’s Web APIs.
Add a Server-Side Send Helper
Create the client at module scope and reuse it. This helper accepts an already authorized and validated business record; do not call it directly with untrusted browser input.lib/ahasend.ts
signupId,
email, and name from your server-side user record. For a backend-to-backend
route, require a service credential, validate the request body and bound its
size, and apply rate limits. Vercel itself rejects a request body over 4.5 MB
with a 413 (FUNCTION_PAYLOAD_TOO_LARGE) before your function runs, which is a
platform ceiling and not a substitute for your own limit. The Next.js
guide provides a complete route-handler example.
An accepted send is multi-status: result.data holds one result per recipient,
and an entry can have status: "error" even when the request resolves. Reuse
the same stable signup ID for retries of the same payload. A stored
non-server-error outcome is replayed for 24 hours; a server error releases the
key for re-execution, so the surrounding workflow must tolerate an uncertain
duplicate.
AhaSend scopes an idempotency key to your account and matches it on the request
method, path, and exact body — never on which API key sent it. That is why
welcomeKey puts sandbox or live in the key itself: without it, a Preview
sandbox send and a Production live send sharing one signup ID would reuse a
single key with two different bodies, which the API rejects as a 422 mismatch.
Set Environment Variables
1
Add production variables
In Project → Settings → Environment Variables, add
AHASEND_API_KEY and AHASEND_ACCOUNT_ID for Production, plus
AHASEND_WEBHOOK_SECRET if you use webhooks.2
Turn on Sensitive before saving each credential
With Sensitive enabled, Vercel stores the value unreadably: it cannot
be retrieved from the dashboard or
vercel env ls afterwards, only
replaced. Do this while adding the variable — converting an existing one
means deleting and re-adding it. Sensitive is available for Production and
Preview, not Development.3
Configure safe previews
For Preview, use a separate least-privilege API key and set
AHASEND_SANDBOX=true. Sandbox sends are validated but not delivered. Do
not make the production API key available to Preview deployments.4
Redeploy
Environment variable changes apply only to new deployments. Create a new
deployment after adding, changing, or rotating a value.
vercel env add AHASEND_API_KEY with no
environment offers every environment at once, which is how a Production key
ends up in Preview.
vercel env add stores Production and Preview values as sensitive by default;
pass --no-sensitive only if you have a reason to keep a value readable.
Local Development
vercel dev downloads Development-scoped variables into memory, so a local
secret file is not required:
vercel env pull .env.local instead, ensure
.env.local is ignored by version control and never commit it.
Webhooks on Vercel
Point the webhook created in your AhaSend dashboard at a stable production route such as:nextRouteHandler from @ahasend/sdk/webhooks. It reads and
caps the exact raw request body, verifies the signature and timestamp, and only
then invokes the application handler. The Next.js guide
contains the complete handler. Its maxBodyBytes option can only narrow the
verifier’s 30 MB ceiling — set it to the size your events actually reach, since
Vercel already returns a 413 above 4.5 MB and the default ceiling is far above
anything a webhook delivery needs.
Keep the webhook route on a domain that Deployment Protection leaves reachable.
Standard Protection covers preview and generated deployment URLs but not
production domains, which is what a webhook endpoint needs. If you switch the
project to All Deployments protection, the production domain requires Vercel
Authentication and AhaSend’s deliveries get a 401. A webhook configuration
carries no custom headers, so the bypass has to travel in the URL: create a
Protection Bypass for Automation secret and append
?x-vercel-protection-bypass=<secret> to the webhook URL. Treat that secret as
a credential — it bypasses protection on every deployment in the project until
you rotate it, and signature verification, not the bypass, is what authenticates
the route.
Timestamp validation is not replay deduplication. Before returning a 2xx,
atomically claim the verified webhook-id and enqueue durable work (or commit
both through an outbox). Acknowledge a duplicate ID without enqueuing it again,
and make consumers idempotent. Vercel Queues uses at-least-once delivery, so a
queue consumer must also tolerate redelivery.
Do not use an untracked promise for required webhook work. Vercel’s
post-response helpers — after() from next/server on Next.js 15.1 and later,
waitUntil() from @vercel/functions otherwise — do keep the work inside the
invocation, but they share the function’s timeout and their promises are
cancelled if it expires. Use them for non-critical work, not as a replacement
for durable persistence.
Going Further
- Framework detail: the Next.js guide covers authenticated route handlers, server actions, and the
nextRouteHandlerwebhook adapter. - Client configuration: see the Node.js SDK guide for timeouts, retries, and idempotency options.
- Durable processing: use a durable queue or outbox for webhook work, and deduplicate both webhook deliveries and at-least-once queue messages.
- Safer previews: keep
AHASEND_SANDBOX=truein Preview and use credentials separate from Production. - Attachments: pass
attachments: [{ data, content_type, file_name, base64: true }]; binary data must be base64 encoded.
Troubleshooting
SDK import fails in an Edge route
SDK import fails in an Edge route
Vercel recommends its default server runtime for new functions. Remove an
unnecessary Edge runtime setting and redeploy. If the route must remain on
Edge, confirm that the failure comes from another dependency or from using
CommonJS rather than ES module imports.
401 from AhaSend after deploying
401 from AhaSend after deploying
Confirm that the API key and account ID are set for the environment you
deployed to, that the key can send from
from.email, and that you created a
new deployment after changing the variables.Preview deployment sends real mail
Preview deployment sends real mail
Give Preview its own restricted key, set
AHASEND_SANDBOX=true, and create
a new Preview deployment. Rotate the Production key if it was exposed to a
Preview environment that should not have it.Webhook deliveries time out or repeat
Webhook deliveries time out or repeat
Verify and atomically persist the delivery before responding, return a 2xx
promptly, and process the durable work idempotently. Do not rely on an
untracked promise or timestamp validation to prevent replay.

