messages:send:{your-domain}. Use messages:send:all when sending from many domains. For management tasks such as creating a webhook, use a separate full key from Account Settings → API Keys with the needed scopes. See send-only credentials and authentication.
Keep credentials on the server. Use sandbox mode for tests. Reuse a stable, business-event idempotency key only with the exact same payload; use different keys for sandbox and live sends. Check every recipient’s status and return a failure when any item is error; HTTP 202 alone does not mean every recipient was accepted.
For webhooks, verify the raw request bytes using the full secret as the raw UTF-8 HMAC key, including its aha-whsec- or aha-rsec- prefix. Never base64-decode the secret. The AhaSend SDK verifiers handle this. These examples use a 30,000,000-byte webhook body limit; a hosting platform may impose a smaller limit. Acknowledge verified unknown event types with 2xx. See verification and retry policy.
Prerequisites
Use Next.js 15.1 or later for the stableafter() API.
- A Next.js project using the App Router
- An AhaSend account with a verified sending domain
- An API key with the
messages:send:{yourdomain.com}scope matching the domain infrom.email(ormessages:send:allto cover every domain), and your account ID
Install the SDK
Try a Sandbox Send
Run this short SDK check locally before adding the longer server integration below. Use a verified sender, a send-only API key, and your account ID. It sends to a reserved test address withsandbox: true, so no email is delivered.
In the server project directory, add .env to .gitignore, then create .env with AHASEND_API_KEY, AHASEND_ACCOUNT_ID, and AHASEND_FROM (an address on your verified domain). Keep these values out of browser code and AI prompts. Use Node.js 22 or newer. Save this as quick-send.mjs:
quick-send.mjs
node --env-file=.env quick-send.mjs from that directory. The output lists each recipient status; queued or scheduled means accepted for sandbox processing. The process exits with a failure if the request or any recipient fails. Continue below for the full integration.
Configure Environment Variables
For local development, add your credentials to an uncommitted.env.local, which Next.js loads automatically. In production, inject the same values through your hosting platform’s secret settings:
.env.local
Create the Client
Build the client on first use and reuse it across requests. Theserver-only marker makes an accidental Client Component import fail at build time, and fromEnv() validates the required AhaSend configuration:
lib/ahasend.ts
Send an Email from a Next.js Route Handler
Route Handlers are public endpoints. This backend-to-backend example requires a long random bearer token over HTTPS; use your application’s existing authentication and authorization instead when the caller is a user. Also configure request-size and rate limits at your hosting layer.lib/internal-auth.ts
app/api/welcome/route.ts
result.data holds one entry per recipient, and an individual recipient can come back with status: "error" and a null id (a suppressed address, for example) while the call itself succeeds. Inspect every entry, not just the first.
The stable, hashed signup ID lets retries across requests reuse the same idempotency key without placing the raw customer identifier in request metadata. Keep the payload stable for a given signup ID; reusing a key with a different payload is rejected.
Alternative: Send from a Server Action
Server Actions are also public mutation endpoints. Authenticate inside the action, then load the recipient from your server-side user record rather than accepting an email address from the browser. AdaptrequireCurrentUser() to your authentication and data-access layer:
app/actions.ts
sandbox: true to validate the request without delivering anything. Idempotency keys are scoped to the account and matched against a hash of the request body, so a sandbox send is not a separate namespace: reusing welcome-<hash> with sandbox flipped is the same key with a different payload, which the API rejects with a 422 for the 24 hours the original record lives. Prefix sandbox keys distinctly (sandbox-welcome-…).
Handle Webhooks
The SDK ships an App Router adapter,nextRouteHandler, that reads a bounded raw body, verifies the HMAC signature and timestamp over those exact bytes, and hands you a typed event. Build it on first request and call it from POST:
app/api/webhooks/ahasend/route.ts
webhook-id header with durable work, acknowledge already-recorded deliveries with a 2xx response, and process the work idempotently. Do not launch untracked work after returning a response: a bare floating promise is killed when the serverless invocation ends, so hand deferred work to after() from next/server, which keeps the invocation alive until it settles. Match the 30,000,000-byte application limit at your host or reverse proxy, configure opaque rejections, and cap concurrent webhook work.
Create the webhook in your AhaSend dashboard pointing at https://your-app.com/api/webhooks/ahasend, and copy its secret into AHASEND_WEBHOOK_SECRET exactly as shown (including the aha-whsec- prefix). Invalid signatures are rejected with a 400 before your handler runs.
Going Further
For substitutions, attachments and scheduled sends, use the sending guide. The Node.js SDK and Go SDK references explain client options, per-recipient results and retries. Use idempotency rules when your application retries a business operation.Troubleshooting
401 AhaSendAuthenticationError
401 AhaSendAuthenticationError
The API key is missing, malformed, or revoked. Verify
AHASEND_API_KEY is set in .env.local (restart next dev after editing it) and that the key exists in your dashboard. In production, set the variable in your hosting provider’s environment settings.API key visible in the browser bundle
API key visible in the browser bundle
You prefixed the variable with
NEXT_PUBLIC_. Rename it to AHASEND_API_KEY, rotate the leaked key in the dashboard, and only read it from server code (Route Handlers, Server Actions, Server Components).400 error mentioning the from address
400 error mentioning the from address
The
from address must belong to a verified sending domain on your account. Check domain status in the dashboard.Related Guides
- Before sending: verify a domain and create a send-only key.
- Other ways to send: REST API, SMTP, CLI quickstart, Node.js SDK and Go SDK.
- Request rules: API authentication, scopes, idempotency, errors and rate limits.
- Testing and events: sandbox mode, CLI webhook testing, event payloads, signature verification and delivery retries.
- Data and limits: retention, tracking and plans and feature availability.

