---
description: "Rasket email API: the API'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

## API rules

- The `rasket` client packages are not published yet: do not install or import `rasket`. Call the REST API at `https://api.rasket.com` with `fetch` (or the project's HTTP client). Paths in the recipe below are relative to that origin.
- Every request sends `Authorization: Bearer ${process.env.RASKET_API_KEY}`, a `User-Agent` naming the project (a request without one is refused with `403 validation_error`) and, with a body, `Content-Type: application/json`. The key comes from `RASKET_API_KEY` and is never written into a file.
- Send `POST /emails` and `POST /emails/batch` with an `Idempotency-Key` (1–256 characters) derived from the work, such as `order-1042`, and repeat the same key on a retry: a repeat returns the first answer instead of sending twice. Retry only a `GET` or a keyed send.
- A non-2xx answer is `{ "statusCode", "name", "message" }`: branch on `name`, not `message`. A `429` carries `retry-after` in whole seconds; wait that long before retrying.
- Verify webhooks with the Svix library (`npm install svix`): the scheme is Svix-compatible, so `new Webhook(secret).verify(rawBody, headers)` checks `svix-id`, `svix-timestamp` and `svix-signature`. Pass the body read as text, never a re-serialised object.

A send, in full:

```ts
const response = await fetch("https://api.rasket.com/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
    "Idempotency-Key": "order-1042",
  },
  body: JSON.stringify({
    from: "Acme <orders@send.example.com>",
    to: ["customer@example.com"],
    subject: "Your order has shipped",
    html: "<p>Order 1042 shipped today.</p>",
  }),
});
if (!response.ok) throw new Error(`Rasket answered ${response.status}`);
const { id } = (await response.json()) as { id: string };
```

## 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: `POST https://api.rasket.com/api-keys`
   - 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: `POST https://api.rasket.com/domains`
   - 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: `GET https://api.rasket.com/domains/{domain_id}`
   - 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: `POST https://api.rasket.com/domains/{domain_id}/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. A call within 15 seconds of the last one, or while a check is running, returns the domain without checking again: wait, then ask again.
5. **Set the sender identity** — Give the team the postal address every marketing email carries in its footer.
   - Call: `PATCH https://api.rasket.com/team`
   - 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: `POST https://api.rasket.com/emails`
   - 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. While the region is still in our sending sandbox, other recipients are refused too. `GET /team` lists those regions in `sending.sandbox_regions`, and the error's `details.recipient` names the refused address.
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: `POST https://api.rasket.com/webhooks`
   - 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` -- writing `html`, or naming a `starter` to copy one of the starter templates -- then publish it so sends can name it by id or alias.
   - Call: `POST https://api.rasket.com/templates/{id}/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: `POST https://api.rasket.com/contacts`
   - 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: `POST https://api.rasket.com/segments`
    - 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. Over MCP, call `preview_broadcast` first and confirm with the person: `send_broadcast` needs the `confirm_token` it returns.
    - Call: `POST https://api.rasket.com/broadcasts/{id}/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: `POST https://api.rasket.com/automations`
    - 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`.
