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.
AhaSend Rules for AI Coding Agents
Add this shared block to your project instructions:@supabase/server to 1.4.1. Its withSupabase helper passes the authenticated Supabase client and user claims to the handler.
Prerequisites
- A Lovable project with Lovable Cloud enabled
- Authentication enabled for the app
- An AhaSend account with a verified sending domain
- An API key with a send scope, plus your account ID
messages:send:{your-domain} rather than messages:send:all, so if the key ever leaks the blast radius is one domain’s outbound mail instead of your whole account.
Install the SDK for a Local Check
Install Deno 2.x on your own computer. Thenpm: import in the example below downloads the SDK; no browser package installation is needed.
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 Deno 2.x. Save this as quick-send.mjs:
quick-send.mjs
deno run --env-file=.env --allow-net=api.ahasend.com --allow-env=AHASEND_API_KEY,AHASEND_ACCOUNT_ID,AHASEND_FROM quick-send.mjs. Deno resolves the npm: import. This is a local SDK check; the server function integration follows below. The output lists recipient statuses and the process fails on a rejected recipient or failed request.
Store the Key as a Secret
Anything the browser can read, a visitor can read, so the key never goes in frontend code or in aVITE_-prefixed variable, which ships to the browser by design.
Lovable Cloud is on by default for most workspaces, and it switches itself on the first time you ask for a feature that needs a backend. That gives your app server-side functions and a place to keep secrets. Open Cloud → Secrets and add three values:
.env, and they do not show up in the published site. That is the whole point.
Prompt Lovable for a Backend Send
Lovable Cloud runs backend integrations in Edge Functions. Import@ahasend/sdk with a Deno npm: specifier, read secrets with Deno.env.get(), and pass them to the constructor.
Ask precisely. The words that matter most are Edge Function: without them, Lovable may call the email API from the client. Spell out the architecture:
“When a signed-in user requests their welcome email, send it through AhaSend from an Edge Function, never from the frontend. Authenticate the caller withThe function it generates should look close to this:withSupabase({ auth: 'user' }), keep JWT verification enabled, and derive the recipient email and stable idempotency key from the verified user claims rather than request JSON. ImportAhaSendClientfromnpm:@ahasend/sdk, readAHASEND_API_KEY,AHASEND_ACCOUNT_ID, andAHASEND_SANDBOXwithDeno.env.get(), reject missing configuration at startup, check every returned recipient status, and never log recipients, content, secrets, request bodies, or whole errors. The frontend should invoke the Edge Function with the signed-in user’s session, not call AhaSend directly.”
supabase/functions/send-welcome-email/index.ts
withSupabase authenticates the caller; the recipient and idempotency key come from verified user claims; and supabase/config.toml does not disable JWT verification for send-welcome-email. The wrapper also handles browser CORS and preflight requests.
AhaSend answers a send with 202 because delivery is asynchronous: the API has accepted and queued your message rather than finished delivering it. The body is multi-status, so result.data carries one entry per recipient, and an individual recipient can come back status: "error" with a null id, a suppressed address for example, while the promise resolves. Check every entry, not just the first. The Edge Function returns its own 202 only after every recipient was accepted.
The stable key protects retries of the same welcome-email operation. Reuse a key only with the exact same payload: AhaSend matches a key against a hash of the request body, so the same key with a changed body is answered 422 rather than replayed. That is why the key carries the environment — the sandbox flag is part of the body, and a key already stored against a sandbox send would reject the first live one.
That key is also the send-rate ceiling on this endpoint. Deriving it from the user id means one account gets one welcome email per idempotency window no matter how many times the button is clicked; the calls after the first replay the stored response instead of mailing again. If Lovable rewrites the key to a fresh UUID per request — a reasonable default in other contexts, and what AhaSend’s own idempotency guide suggests for one-off retries — that ceiling disappears and anyone who can sign up can make your account send on demand. For durable exactly-once behavior beyond the API’s idempotency window, atomically claim a welcome-email job in your database and process it from a retryable outbox or worker.
Verify Where the Key Landed
Lovable’s preview will not catch this mistake for you. Use Lovable’s code view to confirm these checks.1
The key is referenced only inside the Edge Function
Via
Deno.env.get(), and nowhere else. If you see your key, anything starting with aha-sk-, or a VITE_AHASEND... variable anywhere in the frontend, send Lovable back: “Move the AhaSend call into the Edge Function and remove the key from the frontend entirely.” Then rotate the key.2
The frontend calls your Edge Function
It should invoke the function through the authenticated Supabase client, which sends the user’s session. The browser should be talking to your own backend, never directly to
api.ahasend.com.3
The function does not trust recipient data from the browser
It must derive the recipient from verified user claims and keep JWT verification enabled. A caller-controlled
email field turns the function into an email relay even when callers must sign in.Test in Sandbox Mode
KeepAHASEND_SANDBOX=true while you build. In sandbox mode AhaSend runs your message through validation and processing, fires the relevant webhooks, shows it in your dashboard logs, and then stops before delivery. It costs nothing and it cannot email a real customer by accident.
Sandbox also lets you rehearse the unhappy paths. Add sandbox_result: "bounce" to the send request and AhaSend simulates a hard bounce so you can see how your app reacts. "defer", "fail", and "suppress" cover the other outcomes, and the full list is in the sandbox mode guide. Run through them once, confirm you get a clean 202 on the happy path, and only then set AHASEND_SANDBOX=false.
Know what that flip costs you. A Lovable project has one Cloud backend and one Secrets store, so the editor preview and the published app read the same AHASEND_SANDBOX: turning it off turns real sending on for both at once, including the next time you click the button in the preview. If you want to keep rehearsing after launch, do it on the AhaSend side instead of with this flag — a credential created in sandbox mode simulates every send made with it regardless of the request, so a separate development project holding a sandbox-mode key cannot mail a real customer even if the flag is wrong. The sandbox mode guide covers creating one.
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
Error importing @ahasend/sdk in the Edge Function
Error importing @ahasend/sdk in the Edge Function
Confirm the import is server-side and uses the Deno npm specifier exactly as shown:
npm:@ahasend/sdk. Then ask Lovable to call the function directly and inspect its Edge Function logs.401 from api.ahasend.com
401 from api.ahasend.com
Re-check Cloud → Secrets, confirm the key has a send scope for the
from domain, and rotate it if it was ever exposed. Secret changes are available to functions without redeploying them.The frontend gets 401 from the Edge Function
The frontend gets 401 from the Edge Function
Make sure the user is signed in and the frontend invokes the function through the authenticated Supabase client. Keep JWT verification enabled; do not make the send function public to work around authentication errors.
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.

