# Resend plugin

The Resend plugin lets a Vaubaan Gateway mission use Resend without handing the agent a credential. Outbound email : transactional sends classified by recipient zone, segments and contacts, broadcast campaigns, sending domains — one resource per domain and per segment.

- Version: `0.3.0` (package `@vaubaan/plugin-resend`)
- Status: stable
- Health: not enough reports yet — [what this means](/plugins/health)
- Source: [plugins/resend](https://github.com/vaubaan/gateway/tree/main/plugins/resend), catalog [risk-catalog.yaml](https://github.com/vaubaan/gateway/blob/main/plugins/resend/src/risk-catalog.yaml) version 4 of 2026-10-14
- How to connect an account: [Connect Resend](/docs/providers/resend)
- How it is modelled: [handbook](https://github.com/vaubaan/gateway/blob/main/docs/handbook/providers/resend.md)

One account is one Resend API key; resources are its sending domains and its contact segments. Sends are told apart by who the email goes to (internal, allow-listed, anyone), and the owner of the account labels each domain and segment with an environment so that staging and production are held separately.

## Permissions

Each permission is a named risk of the plugin's catalog: a mission holds an explicit list of them, never "all". The severity is computed from the permission's six axes (undo, blast radius, data, cost, visible to, lasts); nothing is decided from it.

| Permission | What it allows | Severity | Why it is a risk |
| --- | --- | --- | --- |
| `resend.read-email-status` | Read email send status and metadata | low | Retrieve the status, subject, from/to and tags of a sent email. Metadata-level : GET /emails/:id returns no body content. Low risk. |
| `resend.read-domains` | Read sending domains and DNS verification status | low | List or retrieve the mission's sending domains with their DKIM / SPF / DMARC records and the recipient-zone configuration (describe\_zones). Reveals the sending posture ; no secret (DNS records are public). |
| `resend.read-audiences` | Read audiences and contacts (PII) — deprecated, replaced by read-segments | medium (deprecated) | Deprecated : matches no call any more. Resend renamed audiences to segments and made contacts account-level ; \`resend.read-segments\` carries the same PII read. Kept so the id stays resolvable. |
| `resend.read-segments` | Read segments and contacts (PII) | medium | List or retrieve the mission's segments, the account's contacts and the members of a segment. Contacts carry PII (email, first\_name, last\_name, custom properties) — GDPR-relevant, every read by an agent is a minimisation question. Distinct from read-broadcasts so a campaign status can be checked without unlocking the contact database. |
| `resend.read-broadcasts` | Read broadcast campaigns | low | List or retrieve broadcast metadata (name, subject, status, schedule) of the mission's segments. No contact-level PII, aggregate campaign information only. |
| `resend.send-email-internal` | Send a transactional email to internal recipients only | low | Every recipient belongs to the sending domain's internal\_domains. Low exfiltration risk — the message stays inside the organisation. Side effect : a real email lands in a real inbox. |
| `resend.send-email-allowlist` | Send a transactional email to pre-approved external recipients | medium | Every recipient is internal or matches allowlist\_domains / allowlist\_addresses of the sending domain. Pre-approved but external : the message leaves the organisation and cannot be recalled. |
| `resend.send-email-external-unbounded` | Send a transactional email to an arbitrary external recipient | high (only for `staging`, `dev`) | At least one recipient matches neither internal\_domains nor the allowlists. Cardinal exfiltration vector : an agent could put sensitive data in the body and send it to an attacker-controlled address, irreversibly. Acceptable only on domains labelled staging or dev. |
| `resend.send-batch` | Send a batch of transactional emails (up to 100 per call) | high | POST /emails/batch sends up to 100 emails in one call. Bulk amplification whatever the recipient zone — the classifier also emits the zone slice, so both permissions must be held. A distinct marker so the creator consents to bulk sending explicitly. |
| `resend.audience-write` | Create or delete an audience — deprecated, replaced by segment-write | medium (deprecated) | Deprecated : matches no call any more. Resend renamed audiences to segments ; \`resend.segment-write\` carries the same structural change. Kept so the id stays resolvable. |
| `resend.segment-write` | Create or delete a segment | medium | Structural change to the contact database. Creating a segment is low risk ; deleting one drops every membership it held (the contacts survive). Conservative vector for the worst case. |
| `resend.contact-write` | Create, update or delete contacts ; add or remove segment members | high | Mutate the contact database : add subscribers, edit PII fields, unsubscribe, delete, or move contacts in and out of a segment (which decides who a broadcast reaches). Reversible for creates, not for deletes. |
| `resend.broadcast-draft` | Create or update a broadcast (draft, not sent) | low | Draft a campaign for a segment of the mission. No side effect until sent — the classifier refuses a create carrying \`send\` or \`scheduled\_at\`, which would bypass broadcast-send. Reversible (delete the draft). |
| `resend.broadcast-send` | Send a broadcast to its segment (mass email delivery) | critical | Deliver a broadcast to every contact of the segment. Cardinal bulk action — potentially thousands of real emails, irreversibly, at a cost that scales with the segment. The plugin re-reads the broadcast upstream and refuses to send one that targets another segment than the one named. The highest-risk operation of this service. |
| `resend.broadcast-delete` | Delete a broadcast | low | Remove a broadcast of a segment of the mission. A sent broadcast loses its record only (delivered emails are not recalled) ; a scheduled one is cancelled. |
| `resend.domain-write` | Add, update or remove a sending domain | high | Posture shift : adding a domain widens what the account can send from ; removing one breaks delivery for every sender on it ; tracking and TLS settings change what recipients see. DNS must be configured outside Resend. Suggested : ask first. |
| `resend.domain-verify` | Trigger DNS verification of a domain | low | Idempotent request that asks Resend to re-check the DNS records of a domain of the mission. No side effect beyond the attempt ; a step of the domain set-up posture shift. |
| `resend.uncategorized-write` | Uncategorized Resend write | medium (the catch-all: touched only by a call no other permission covers) | Catch-all for a write no named permission covers. If Resend adds a surface (webhooks, API keys, templates) and Vaubaan wraps it before the catalog is updated, calls land here — fail-loud by design. Never offered in a preset. |

## Presets

A preset pre-fills the permissions of a mission for one account, with a limit per period, a rate and whether each permission runs freely or asks the mission creator first. The person creating the mission adjusts it; the mission stores the explicit list.

### Read-only

`read-only` — Email status, sending domains and campaign metadata of the selected resources. No PII, no write.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `resend.read-email-status` | Read email send status and metadata | 2000 | 120 per minute | runs freely |
| `resend.read-domains` | Read sending domains and DNS verification status | 1000 | 60 per minute | runs freely |
| `resend.read-broadcasts` | Read broadcast campaigns | 1000 | 60 per minute | runs freely |

### Developer

`developer` — One mission per code project — transactional sends to internal and allow-listed recipients, generous limits; no bulk, no PII, no domain change.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `resend.read-email-status` | Read email send status and metadata | 5000 | 120 per minute | runs freely |
| `resend.read-domains` | Read sending domains and DNS verification status | 2000 | 60 per minute | runs freely |
| `resend.read-broadcasts` | Read broadcast campaigns | 1000 | 60 per minute | runs freely |
| `resend.send-email-internal` | Send a transactional email to internal recipients only | 2000 | 60 per minute | runs freely |
| `resend.send-email-allowlist` | Send a transactional email to pre-approved external recipients | 1000 | 30 per minute | runs freely |

### Marketing operator

`marketing-operator` — Contacts, segments and campaigns; broadcast sends, batch sends and segment deletion ask first. No domain change.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `resend.read-email-status` | Read email send status and metadata | 2000 | 120 per minute | runs freely |
| `resend.read-domains` | Read sending domains and DNS verification status | 1000 | 60 per minute | runs freely |
| `resend.read-segments` | Read segments and contacts (PII) | 5000 | 120 per minute | runs freely |
| `resend.read-broadcasts` | Read broadcast campaigns | 2000 | 120 per minute | runs freely |
| `resend.send-email-internal` | Send a transactional email to internal recipients only | 1000 | 60 per minute | runs freely |
| `resend.send-email-allowlist` | Send a transactional email to pre-approved external recipients | 1000 | 30 per minute | runs freely |
| `resend.send-batch` | Send a batch of transactional emails (up to 100 per call) | 50 | 5 per minute | ask me first |
| `resend.contact-write` | Create, update or delete contacts ; add or remove segment members | 5000 | 120 per minute | runs freely |
| `resend.segment-write` | Create or delete a segment | 20 | 5 per minute | ask me first |
| `resend.broadcast-draft` | Create or update a broadcast (draft, not sent) | 200 | 20 per minute | runs freely |
| `resend.broadcast-send` | Send a broadcast to its segment (mass email delivery) | 10 | 1 per minute | ask me first |
| `resend.broadcast-delete` | Delete a broadcast | 50 | 10 per minute | runs freely |

### Domain admin

`domain-admin` — Provision sending domains — read and verify freely, every domain mutation asks first. No send.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `resend.read-domains` | Read sending domains and DNS verification status | 2000 | 60 per minute | runs freely |
| `resend.domain-verify` | Trigger DNS verification of a domain | 200 | 10 per minute | runs freely |
| `resend.domain-write` | Add, update or remove a sending domain | 20 | 5 per minute | ask me first |

## Tools

| Tool | What it does |
| --- | --- |
| `resend__get_email` | Read the status of a sent email |
| `resend__list_domains` | List sending domains |
| `resend__get_domain` | Read a sending domain |
| `resend__list_segments` | List segments |
| `resend__get_segment` | Read a segment |
| `resend__list_contacts` | List contacts |
| `resend__get_contact` | Read a contact |
| `resend__list_broadcasts` | List broadcasts |
| `resend__get_broadcast` | Read a broadcast |
| `resend__describe_zones` | Read the recipient zones |
| `resend__send_email` | Send an email |
| `resend__send_batch_emails` | Send a batch of emails |
| `resend__create_domain` | Add a sending domain |
| `resend__update_domain` | Change a sending domain |
| `resend__delete_domain` | Remove a sending domain |
| `resend__verify_domain` | Re-check a sending domain |
| `resend__create_segment` | Create a segment |
| `resend__delete_segment` | Delete a segment |
| `resend__create_contact` | Create a contact |
| `resend__update_contact` | Change a contact |
| `resend__delete_contact` | Delete a contact |
| `resend__add_contact_to_segment` | Add a contact to a segment |
| `resend__remove_contact_from_segment` | Remove a contact from a segment |
| `resend__create_broadcast` | Create a draft broadcast |
| `resend__update_broadcast` | Change a draft broadcast |
| `resend__send_broadcast` | Send a broadcast to its segment |
| `resend__delete_broadcast` | Delete a broadcast |

## Health

Health: not enough reports yet. A plugin version is deemed validated by the community with at least 200 reports and 90 % of them successful, over the last 90 days, as reported by the instances whose operators turned community telemetry on. It is a community signal, not a security guarantee.
