Prerequisites
Cascade writes and runs code, but it can’t verify your domain or create your credentials. Do these in the AhaSend dashboard first.1
Verify a Sending Domain
Add the SPF, DKIM, and DMARC records from the domain setup guide, then confirm the domain is verified before sending.
2
Create an API Key
Follow the API credentials guide and use a domain scope such as
messages:send:{example.com} instead of messages:send:all: if it leaks, the damage is limited to one domain’s outbound mail. Domain-specific scopes are written with the curly braces around the domain. Grab your account ID too.3
Put Both in Your Environment
For local agent-run tests, create a dedicated, domain-scoped credential in Sandbox mode. Set its
AHASEND_API_KEY, AHASEND_ACCOUNT_ID, and AHASEND_DELIVERY_MODE=sandbox in .env, and keep that file out of git. Configure live credentials in your deployment’s secret manager rather than giving them to an agent-run test.Step 1: Install the SDK Before You Prompt
Do this yourself. An agent that finds the dependency already inpackage.json has far less room to improvise a different one.
Step 2: Give Cascade the Ground Truth with a Rules File
Give Cascade an authoritative project Rule instead of relying on generated code to reconstruct the integration from general context. Cascade can also search documentation, but a Rule keeps the critical constraints available inside the repository. Workspace Rules are Markdown files with an activation mode, discovered under.devin/rules/ in your workspace — the preferred location in current Windsurf builds, with the older .windsurf/rules/ still read as a fallback. Create a model-decision rule so Cascade loads the full instructions when its description matches an email task:
.devin/rules/ahasend-email.md
https://ahasend.com/docs/llms.txt, ask it to write the rule from those pages, then check the result against the list above.
Step 3: Capture the Integration as a Workflow
A Workflow is a Markdown file under.windsurf/workflows/ with a short frontmatter description and a series of steps: a saved prompt-plus-procedure that Cascade can replay instead of you re-explaining the task each time.
Workflows are manual and specific to Cascade. Open a Cascade session and invoke this one with /ahasend-email; another agent mode will not run it, and Cascade will not select it automatically.
.windsurf/workflows/ahasend-email.md
Step 4: Prompt Cascade with Specifics
Whether you’re invoking the Workflow or asking directly, name the file and the behavior:- Weak: “add email to my app”
- Better: “In the signup handler, after the user is created, send a welcome email through AhaSend using
/ahasend-email. Reuse the client fromlib/ahasend.ts, use the server-side signup event ID for idempotency, and await the send. If response latency matters, enqueue a durable job and send from its worker.”
lib/ahasend.ts
routes/signup.ts
Step 5: Test in Sandbox Mode
Cascade can run terminal commands, but a production-capable API key remains production-capable even when one request setssandbox: true. Prefer mocked tests. If you explicitly approve a live integration check, use a dedicated domain-scoped credential configured in Sandbox mode. Sandbox mode validates and processes the message, triggers relevant configured webhooks, consumes no email credits, and never delivers it.
status === "error". Swap sandbox_result to "deliver", "bounce", "defer", "fail", or "suppress" to trigger the applicable configured webhook event. A webhook test also needs a configured endpoint that AhaSend can reach. Keep delivery fail-closed through AHASEND_DELIVERY_MODE; do not infer live delivery from a generic environment name.
Step 6: Read the Diff
Confirm these before you commit:- The real package. The import must be
@ahasend/sdk. If you see another package name, or rawfetchcalls in a plain Node service, a guess slipped past the rule. - No open relay. If Cascade added an HTTP route that sends, check that the recipient comes from the authenticated session or a server-side record rather than the request body, that the route is authenticated and rate-limited, and that the body size is bounded. A handler taking
to,subject, andhtmlfrom an unauthenticated caller is a spam relay on your verified domain. - Every recipient checked. A promise that resolves means the request was accepted, not that every address was. Code reading
result.data[0]and moving on will silently miss a suppressed recipient in a batch. - The key comes from the environment. An agent editing several files can drift a secret into the wrong one. Confirm the key is read from
process.envand never written to anything git tracks.
Going Further
- Write a second workflow for the failure path: one that walks
sandbox_resultthroughbounceandsuppress, so rehearsing a rejection is a slash command rather than a memory exercise. - Templating and webhooks: per-recipient
substitutionscover personalised batches, and@ahasend/sdk/webhooksverifies delivery-event signatures. Add both to your rules file so Cascade reaches for them unprompted. - Same grounding trick, different file: Cursor reads
.cursor/rules/, Claude readsCLAUDE.md, and GitHub Copilot reads.github/copilot-instructions.md. - For the finished shape of what Cascade is building, see Express, Fastify, or SvelteKit.
Troubleshooting
Cascade ignores the Rules file
Cascade ignores the Rules file
Confirm the file is under
.devin/rules/ in the current workspace and has valid trigger frontmatter. With model_decision, Cascade sees the description first and loads the rule when it decides that description is relevant. Make that description more specific, or change the trigger to always_on if the full rule must be included on every request.The Workflow runs but skips the sandbox check
The Workflow runs but skips the sandbox check
Workflow steps are instructions, not a script. Make the last step an explicit, checkable assertion (“confirm no entry in result.data has status error before reporting done”) rather than a vague “test it”.
The generated code throws AhaSendConfigurationError on startup
The generated code throws AhaSendConfigurationError on startup
If Cascade put the client in frontend code, move the send to a server-side route. If it is already server-side, verify the required environment variables and account ID. The browser check is defense in depth, not a substitute for keeping credentials out of client bundles.
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, and see the quickstart if you haven’t added one yet.
