---
description: "Rasket email API: the client's rules and the order to set Rasket up in."
globs:
alwaysApply: false
---

<!-- Generated by `pnpm --filter @rasket/api recipe:generate` from `packages/api/src/mcp/recipe.ts`. Do not edit; regenerate. -->

# Rasket

## Client rules

- Construct one client: `new Rasket({ apiKey: process.env.RASKET_API_KEY, userAgent: "acme-billing/1.0" })`. The key comes from `RASKET_API_KEY` and is never written into a file. The default origin is `https://api.rasket.com`; pass `baseUrl` only for a deployment host.
- Every method resolves to `{ body, rateLimit, requestId, idempotentReplayed }` or throws `RasketApiError` (an HTTP answer; branch on `.name`, not `.message`) or `RasketConnectionError` (no answer arrived).
- Pass `{ idempotencyKey }` (1–256 characters) to `emails.send` and `batch.send`. Only a `GET` or a keyed send is ever retried.
- Verify webhooks with `rasket.webhooks.verify(rawBody, headers, secret)` over the body read as text, never a re-serialised object.
- The Python client has the same methods with snake_case names: `rasket.api_keys.create` for `rasket.apiKeys.create`.

## Setting Rasket up

Follow these steps in order and confirm each **Done when** before the next.

1. **Create an API key** — Mint the `full_access` key the rest of the setup runs with. The first key of a team is created in the dashboard; a `full_access` key can mint more.
   - Call: `rasket.apiKeys.create()`
   - Needs: a `full_access` key
   - Done when: The response's `token` is exported as `RASKET_API_KEY` in the environment the agent runs in, and appears in no file.
   - Commonest failure: The token is shown once and cannot be read back. A lost token means creating another key, not retrieving the old one.
2. **Add a domain** — Add the domain mail is sent from. A subdomain such as `send.example.com` keeps its records apart from the apex's.
   - Call: `rasket.domains.create()`
   - Needs: a `full_access` key
   - Done when: The response carries the domain's `id` and the `records` to publish.
   - Commonest failure: `409`: another team has already verified this name. List the team's domains first, so a second run does not try to add the same one again.
3. **Publish its DNS records** — Read the domain's records and publish every one of them at the DNS host.
   - Call: `rasket.domains.get()`
   - Needs: a `full_access` key
   - Done when: Every entry in `records` exists at the DNS host with its exact `type`, `name` and `value`, and its `priority` on an `MX` record.
   - Commonest failure: Most DNS hosts append the zone to the name, so pasting the full name publishes `send.example.com.example.com`. Enter only the part before the zone.
4. **Verify the domain** — Ask Rasket to look the records up now rather than at its next scheduled check.
   - Call: `rasket.domains.verify()`
   - Needs: a `full_access` key
   - Done when: Reading the domain again shows `status` `verified`; each record carries its own `status` while it is not.
   - Commonest failure: Asking before DNS has propagated. The call is limited to one a minute per domain (`429`) and answers `409 resource_locked` while a check is running: wait, then ask again.
5. **Set the sender identity** — Give the team the postal address every marketing email carries in its footer.
   - Call: `rasket.team.update()`
   - Needs: a `full_access` key
   - Done when: The team answers with its postal address set.
   - Commonest failure: Skipping it. Transactional mail sends without one, but sending a broadcast is refused with `422 validation_error` until the team has a postal address.
6. **Send an email** — Send one message from an address on the verified domain, with an `Idempotency-Key` so a retry cannot send it twice.
   - Call: `rasket.emails.send()`
   - Needs: a `sending_access` key
   - Done when: The response carries the email's `id`, and reading `/emails/{email_id}` shows what happened to each recipient.
   - Commonest failure: A `from` address on a domain that is not verified yet is refused with `403 validation_error`. Verify the domain first.
7. **Add a webhook and check its signature** — Register an HTTPS endpoint for the events the project handles. The response carries the signing secret, once.
   - Call: `rasket.webhooks.create()`
   - Needs: a `full_access` key
   - Done when: The endpoint verifies a delivery's `svix-id`, `svix-timestamp` and `svix-signature` headers against the signing secret, which lives in the project's secret store.
   - Commonest failure: Verifying a parsed and re-serialised body. The signature covers the exact bytes received, so read the request body as text before verifying it.
8. **Create and publish a template** — Create a template with `POST /templates`, then publish it so sends can name it by id or alias.
   - Call: `rasket.templates.publish()`
   - Needs: a `full_access` key
   - Done when: The template has a published version.
   - Commonest failure: Editing a published template and expecting sends to change. Sends resolve to the published version until you publish again.
9. **Add a contact** — Add a contact, recording only the consent the project actually holds.
   - Call: `rasket.contacts.create()`
   - Needs: a `full_access` key
   - Done when: The response carries the contact's `id`. Creating the same address again returns that id, never a duplicate.
   - Commonest failure: A `properties` key that is not a declared contact property, or a value of the wrong type, is `422 validation_error`. Declare the property first.
10. **Create a segment** — Define the audience a broadcast goes to: by filter, by hand, or both.
    - Call: `rasket.segments.create()`
    - Needs: a `full_access` key
    - Done when: Listing the segment's contacts returns the contacts you expect.
    - Commonest failure: A filter naming an undeclared property is `422 validation_error`, and a filter cannot be changed later: create a new segment instead.
11. **Send a broadcast** — Create a broadcast to the segment with `POST /broadcasts`, then send it or schedule it.
    - Call: `rasket.broadcasts.send()`
    - Needs: a `full_access` key
    - Done when: The broadcast moves to `queued`, or to `scheduled` when `scheduled_at` was given.
    - Commonest failure: The compliance gate refuses before anything is queued: no verified sending domain, no postal address on the team, or a segment that does not resolve.
12. **Create an automation** — Create a workflow that runs per contact, starting from exactly one `trigger` step.
    - Call: `rasket.automations.create()`
    - Needs: a `full_access` key
    - Done when: The automation exists with version 1 of its graph. It is `disabled` until you enable it, so nothing enrols by accident.
    - Commonest failure: A graph without exactly one `trigger`, with an unreachable step or a cycle, or naming a template, segment or event that does not exist, is `422 validation_error`.
