Prerequisites
Ensure Copilot cloud agent is enabled for the target repository and that you have write access to it. Without both, Copilot cannot be assigned the issue. The agent can write the integration, but it cannot verify your domain or create your credentials. Do that in the AhaSend dashboard first:- Verify a sending domain. Add the SPF, DKIM, and DMARC records from the domain setup guide.
- Create an API key. Follow the API credentials guide and scope it to
messages:send:{your-domain}instead ofmessages:send:all. Note your account ID.
Step 1: Install the SDK and Commit It
Install the official SDK and commit the manifest and lockfile before assigning the task.Step 2: Write the Issue
Give the agent the API contract, security boundaries, and acceptance tests instead of leaving it to infer them. Open an issue, use a prompt like this, and assign it to Copilot:Title: Add order-confirmation email via AhaSend Body: When an order is created, send a confirmation email using the official AhaSend TypeScript SDK,Copilot cloud agent also supports repository-wide instructions in@ahasend/sdk, which is already a dependency. Do not hand-roll HTTP calls or add another email package.
- Validate
AHASEND_API_KEY,AHASEND_ACCOUNT_ID, and the sender address at startup. Construct oneAhaSendClientat module scope withapiKeyandaccountId.- Send with
client.messages.send({ from, recipients, subject, html_content, text_content }). Body fields are snake_case.- Pass a stable, server-derived business identifier as
options.idempotencyKeyso an application-level retry does not create a second email. Reuse a key only for the same exact payload, and include the delivery mode in the key: keys live in one account-wide namespace shared by sandbox and live traffic, and the API matches them against a hash of the request body.- Treat the response as multi-status: inspect every entry in
result.data. A recipient can havestatus: "error"and a nullideven though the promise resolved.- Catch
AhaSendAPIErrorfrom@ahasend/sdkand branch on structured fields, not message text. Logs may include only aggregate counts, HTTP status, SDK error code, and request ID. Never log addresses, content, bodies, headers, idempotency keys, or whole request, response, event, client, or error objects.- Keep API keys and all AhaSend calls in server-only code. Authenticate and authorize the caller, validate input, and load the order and recipient from server-authoritative storage; do not trust a client-supplied email address or order ID. Bound the request body and rate-limit the triggering route per authenticated caller so a looping or hostile client cannot mail-bomb a customer. Escape any untrusted value before inserting it into HTML.
- Unit tests must mock the API boundary. Any explicitly approved live integration test must set
sandbox: trueand assert every recipient result.- If the task involves webhooks, import
WebhookVerifierfrom@ahasend/sdk/webhooks, verify the exact raw body, andawait verifier.parse(headers, rawBody). Enforce a request-size limit, and commit the verifiedwebhook-idand the durable work it guards in one transaction before any side effect, then run that work from a worker outside the request. A bare unique-ID insert loses the event if the process dies after the insert but before processing, and timestamp verification alone does not prevent replays.
.github/copilot-instructions.md. Put durable rules such as server-only credentials, stable idempotency keys, multi-status checks, and safe logging there so later email tasks inherit them.
Step 3: Review the Pull Request
When the agent finishes, it requests your review. For a new task, GitHub limits it to a newly createdcopilot/ branch, and Copilot cannot approve or merge the pull request. Review the diff and the agent’s session log before allowing workflows or merging.
Did the key stay server-side?
Scan the entire diff for hardcoded keys and for client-exposed variables such asNEXT_PUBLIC_ or VITE_. Verify that the call is reachable only after your application’s normal authentication and authorization checks, that the recipient comes from trusted server-side data, and that the route bounds its request body and rate-limits each authenticated caller. A send path that any caller can reach, or reach without a ceiling, is a mail relay regardless of how correct the SDK usage is. If any of those checks fail, request a correction with an @copilot comment on the pull request.
Is it using the SDK safely?
A representative server-side service looks like this:lib/send-order-confirmation.ts
Does it handle retries and multi-status results?
The SDK generates an idempotency key for its own retry loop, but a stable caller-provided key is needed when your application may invoke the business operation again. Confirm the PR derives that key from a trusted immutable ID and never logs it. Keys are scoped to the account, not to a delivery mode, and the API matches a reused key against a hash of the request body. A key built from the order ID alone therefore collides between the sandbox run of Step 4 and the first live send for the same order: the bodies differ only in thesandbox field, so the live request is rejected as an idempotency mismatch instead of being sent. Including the mode in the key, as above, keeps the two namespaces apart.
A resolved send returns one entry per recipient. Do not accept code that checks only result.data[0], treats resolution as unconditional success, or exposes recipient details in a response or log.
Step 4: Test Without Giving the Agent a Production Credential
Prefer mocked tests: Copilot can validate request construction, error handling, multi-status behavior, and idempotency without any AhaSend credential. If you deliberately want the agent to call the real AhaSend API in sandbox mode, create a dedicated, domain-scoped API key that you can revoke immediately afterward. In the repository, go to Settings → Secrets and variables → Agents, store it asAHASEND_API_KEY, store the account ID as an Agents secret or variable, and add Agents variables for AHASEND_FROM_ADDRESS and AHASEND_DELIVERY_MODE. The sender must use the verified domain, and the delivery mode must be sandbox. A sandbox send validates the request without delivering email; use sandbox_result values such as deliver, bounce, defer, fail, and suppress to exercise different outcomes.
This is an ordinary API key, not a sandbox-restricted credential. Agents secrets are exposed as environment variables to scripts and tools Copilot runs, and sandbox: true is only a request field. Only provide the key if you accept that risk. Keep the agent firewall enabled and explicitly allow api.ahasend.com if the request is blocked; do not disable the firewall just to make the test pass.
Remove the temporary Agents credential after the test. Your deployed application should use a separate key from its hosting platform’s secret store.
Step 5: Merge and Send a Real Email
By default, GitHub Actions workflows do not run automatically when Copilot pushes. First inspect the diff, especially changes under.github/workflows/, then use Approve and run workflows and confirm the required checks actually completed. Your own approval does not count toward required approvals on a Copilot pull request, so obtain another approval if the repository requires one.
After review and CI pass, merge through your normal protected-branch process. In the deployed environment:
- Set
AHASEND_API_KEY,AHASEND_ACCOUNT_ID, andAHASEND_FROM_ADDRESSin the hosting platform’s secret store. - Set
AHASEND_DELIVERY_MODEexplicitly tolive; fail closed when it is absent or invalid. - Send one confirmation to an address you control and verify the result and delivery.
Going Further
- Exercise sandbox outcomes: integration tests can set
sandbox_resulttobounceorsuppressto exercise the corresponding delivery and webhook paths. Assert multi-status send results separately. - Templating and webhooks: per-recipient
substitutionssupport personalized batches, and@ahasend/sdk/webhooksverifies delivery-event signatures. Preserve the raw body, and commitwebhook-idtogether with the durable work in one transaction before any side effect. - Same grounding technique, different instruction file: Cursor, Claude, and Windsurf.
- For complete framework integrations, see Next.js, Express, or Remix.
Troubleshooting
The PR hand-rolls HTTP calls instead of using the SDK
The PR hand-rolls HTTP calls instead of using the SDK
Confirm the dependency and lockfile were committed before the task started. Comment
@copilot replace the raw HTTP calls with the @ahasend/sdk client, then add the constraint to .github/copilot-instructions.md.The integration test gets an authentication error
The integration test gets an authentication error
Confirm that any deliberately provided credential is stored under Secrets and variables → Agents, not under Actions, Codespaces, or Dependabot. Check its scope and revocation state without printing it. Prefer a mocked test if external access is unnecessary.
The sandbox request is blocked
The sandbox request is blocked
Copilot cloud agent’s firewall may block the AhaSend API host. Keep the firewall enabled and add
api.ahasend.com to its allowlist only if you have accepted the credential risk. Otherwise, use a mocked test.The generated code throws in a browser
The generated code throws in a browser
The API client belongs in server-only code. Move the AhaSend client and send call behind an authenticated server route or controller, and ensure no client component imports that module.

