Skip to main content
The AhaSend SDK runs in Vercel Functions. This guide covers deployment; for complete application handlers, see the Next.js and Express guides.

Prerequisites

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 accept runtime = 'edge' at all. If an older route still uses the Edge runtime, the SDK works there too using the runtime’s Web APIs.
The runtime choice does not change the security boundary. Import the SDK only in server code and never expose an API key to browser code.

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
Call this helper only after authenticating and authorizing the request. Every Vercel deployment, Preview included, is served from a public generated URL, so a route that reaches this helper without an authorization check is an open mail relay for anyone who finds the URL. For a browser flow, derive 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.
You can also add values interactively with the CLI. Name the target environment on every command: 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.
Never give these variables a framework’s public prefix — NEXT_PUBLIC_ in Next.js, VITE_ in Vite, NUXT_PUBLIC_ in Nuxt. Any of them inlines the value into the browser bundle. Rotate an API key immediately if it is exposed.

Local Development

vercel dev downloads Development-scoped variables into memory, so a local secret file is not required:
If you deliberately use 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:
In Next.js, use 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 nextRouteHandler webhook 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=true in Preview and use credentials separate from Production.
  • Attachments: pass attachments: [{ data, content_type, file_name, base64: true }]; binary data must be base64 encoded.

Troubleshooting

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.
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.
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.
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.