vybekiit 0.7.33 → 0.7.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +818 -92
- package/dist/global-skills/eas-app-stores/SKILL.md +2 -2
- package/dist/global-skills/eas-hosting/SKILL.md +2 -2
- package/dist/global-skills/eas-observe/SKILL.md +7 -15
- package/dist/global-skills/eas-observe/references/queries.md +51 -10
- package/dist/global-skills/eas-observe/references/setup.md +60 -7
- package/dist/global-skills/eas-simulator/SKILL.md +2 -2
- package/dist/global-skills/eas-simulator/references/run-your-app.md +2 -0
- package/dist/global-skills/eas-update/SKILL.md +2 -2
- package/dist/global-skills/eas-update-insights/SKILL.md +2 -2
- package/dist/global-skills/eas-workflows/SKILL.md +2 -2
- package/dist/global-skills/expo-animation/SKILL.md +2 -2
- package/dist/global-skills/expo-app-clip/SKILL.md +2 -2
- package/dist/global-skills/expo-brownfield/SKILL.md +2 -2
- package/dist/global-skills/expo-data-fetching/SKILL.md +2 -2
- package/dist/global-skills/expo-design-system/SKILL.md +2 -2
- package/dist/global-skills/expo-dev-client/SKILL.md +4 -2
- package/dist/global-skills/expo-dom/SKILL.md +2 -2
- package/dist/global-skills/expo-examples/SKILL.md +2 -2
- package/dist/global-skills/expo-migrate-module/SKILL.md +2 -2
- package/dist/global-skills/expo-module/SKILL.md +2 -2
- package/dist/global-skills/expo-native-ui/SKILL.md +2 -2
- package/dist/global-skills/expo-overview/SKILL.md +9 -4
- package/dist/global-skills/expo-overview/references/launch-without-dev-menu.md +24 -0
- package/dist/global-skills/expo-project-structure/SKILL.md +2 -2
- package/dist/global-skills/expo-router/SKILL.md +2 -2
- package/dist/global-skills/expo-ui/SKILL.md +2 -2
- package/dist/global-skills/expo-upgrade/SKILL.md +2 -2
- package/dist/global-skills/expo-web-to-native/SKILL.md +2 -2
- package/dist/global-skills/expo-web-to-native/references/verify-on-device.md +1 -1
- package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +16 -3
- package/dist/global-skills/firebase-app-hosting-basics/references/cli_commands.md +10 -12
- package/dist/global-skills/firebase-app-hosting-basics/references/configuration.md +2 -1
- package/dist/global-skills/firebase-basics/SKILL.md +6 -4
- package/dist/global-skills/firebase-basics/references/web_setup.md +3 -3
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +81 -333
- package/dist/global-skills/instrument-feature-flags/references/android.md +19 -13
- package/dist/global-skills/instrument-feature-flags/references/api.md +17 -13
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +35 -35
- package/dist/global-skills/instrument-feature-flags/references/django.md +120 -72
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +48 -102
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +4 -10
- package/dist/global-skills/instrument-feature-flags/references/flask.md +55 -39
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +19 -13
- package/dist/global-skills/instrument-feature-flags/references/go.md +21 -13
- package/dist/global-skills/instrument-feature-flags/references/ios.md +19 -13
- package/dist/global-skills/instrument-feature-flags/references/java.md +4 -12
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +18 -16
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +44 -54
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +21 -13
- package/dist/global-skills/instrument-feature-flags/references/php.md +21 -13
- package/dist/global-skills/instrument-feature-flags/references/python.md +22 -14
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +18 -13
- package/dist/global-skills/instrument-feature-flags/references/react.md +21 -14
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +31 -68
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +21 -13
- package/dist/global-skills/instrument-feature-flags/references/rust.md +10 -34
- package/dist/global-skills/instrument-feature-flags/references/usage.md +94 -114
- package/dist/global-skills/instrument-feature-flags/references/web.md +21 -13
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-android.md +7 -7
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-expo.md +2 -2
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-fastapi.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-flask.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-laravel.md +2 -2
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-php.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-python.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-swift.md +4 -4
- package/dist/global-skills/instrument-product-analytics/references/android.md +64 -109
- package/dist/global-skills/instrument-product-analytics/references/angular.md +32 -28
- package/dist/global-skills/instrument-product-analytics/references/astro.md +29 -34
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +27 -42
- package/dist/global-skills/instrument-product-analytics/references/django.md +120 -72
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +48 -102
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +25 -50
- package/dist/global-skills/instrument-product-analytics/references/flask.md +55 -39
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +94 -96
- package/dist/global-skills/instrument-product-analytics/references/go.md +39 -77
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +24 -26
- package/dist/global-skills/instrument-product-analytics/references/ios.md +22 -22
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +18 -16
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +44 -54
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +32 -34
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +36 -36
- package/dist/global-skills/instrument-product-analytics/references/php.md +42 -87
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +15 -12
- package/dist/global-skills/instrument-product-analytics/references/python.md +106 -154
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +97 -176
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +29 -40
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +29 -38
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +29 -40
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +32 -37
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +31 -68
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +40 -96
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +34 -34
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +19 -19
- package/dist/global-skills/instrument-product-analytics/references/usage.md +94 -114
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +33 -45
- package/dist/global-skills/manifest.json +2 -1
- package/dist/global-skills/mongodb-search-and-ai/SKILL.md +40 -22
- package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +8 -2
- package/dist/global-skills/mongodb-search-and-ai/references/quick-start.md +725 -0
- package/dist/global-skills/mongodb-search-and-ai/scripts/quickstart_complete.py +386 -0
- package/dist/global-skills/neon/SKILL.md +15 -10
- package/dist/global-skills/neon/references/claimable-neon.md +2 -0
- package/dist/global-skills/neon-functions/SKILL.md +12 -11
- package/dist/global-skills/neon-functions/references/sse.md +2 -2
- package/dist/global-skills/neon-object-storage/SKILL.md +5 -4
- package/dist/global-skills/neon-postgres/SKILL.md +4 -1
- package/dist/global-skills/neon-realtime/SKILL.md +207 -0
- package/dist/global-skills/neon-realtime/references/mutations.md +743 -0
- package/dist/global-skills/neon-realtime/references/queries.md +509 -0
- package/dist/global-skills/neon-realtime/references/reactivity.md +407 -0
- package/dist/global-skills/neon-realtime/references/ssr.md +695 -0
- package/dist/global-skills/neon-realtime/references/subscribing.md +485 -0
- package/dist/global-skills/resend/SKILL.md +4 -2
- package/dist/global-skills/resend/references/inboxes.md +377 -0
- package/dist/global-skills/resend/references/webhooks.md +9 -0
- package/dist/global-skills/stripe-best-practices/SKILL.md +1 -1
- package/dist/global-skills/stripe-best-practices/references/connect.md +1 -1
- package/dist/global-skills/stripe-best-practices/references/security.md +9 -9
- package/dist/global-skills/vybekiitReferences/backend/AGENTS.md +1 -1
- package/dist/global-skills/vybekiitReferences/backend/agent/goal-routes.json +303 -18
- package/dist/global-skills/vybekiitReferences/extension/AGENTS.md +1 -1
- package/dist/global-skills/vybekiitReferences/extension/agent/goal-routes.json +352 -18
- package/dist/global-skills/vybekiitReferences/mobile/AGENTS.md +1 -1
- package/dist/global-skills/vybekiitReferences/mobile/agent/goal-routes.json +413 -22
- package/dist/global-skills/vybekiitReferences/spa/AGENTS.md +1 -1
- package/dist/global-skills/vybekiitReferences/spa/agent/goal-routes.json +391 -21
- package/dist/global-skills/vybekiitReferences/web/AGENTS.md +14 -2
- package/dist/global-skills/vybekiitReferences/web/agent/goal-routes.json +490 -32
- package/package.json +8 -8
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# Inboxes
|
|
2
|
+
|
|
3
|
+
An inbox sends and receives email at one address on your domain. Received and sent emails group into threads. Each thread is in one folder and can carry labels. Drafts hold unsent emails, and agent settings tell an AI agent how to handle the threads of the inbox.
|
|
4
|
+
|
|
5
|
+
Inboxes need Node.js `resend` 6.34.1 or later, or Python `resend` 2.50.0 or later.
|
|
6
|
+
|
|
7
|
+
If an agent acts on the content of received emails, also install the `agent-email-inbox` skill. It covers the security levels for untrusted input.
|
|
8
|
+
|
|
9
|
+
## SDK Methods
|
|
10
|
+
|
|
11
|
+
| Operation | Node.js | Python |
|
|
12
|
+
|-----------|---------|--------|
|
|
13
|
+
| Create inbox | `resend.inboxes.create(params)` | `resend.Inboxes.create(params)` |
|
|
14
|
+
| List inboxes | `resend.inboxes.list(params?)` | `resend.Inboxes.list(params?)` |
|
|
15
|
+
| Get inbox | `resend.inboxes.get(idOrEmail)` | `resend.Inboxes.get(inbox_id)` |
|
|
16
|
+
| Update inbox | `resend.inboxes.update(id, params)` | `resend.Inboxes.update(params)`, `id` inside params |
|
|
17
|
+
| Delete inbox | `resend.inboxes.remove(id)` | `resend.Inboxes.remove(inbox_id)` |
|
|
18
|
+
| Get agent settings | `resend.inboxes.agent.get({ inboxId })` | `resend.Inboxes.Agent.get(inbox_id)` |
|
|
19
|
+
| Update agent settings | `resend.inboxes.agent.update({ inboxId, ...params })` | `resend.Inboxes.Agent.update(params)`, `inbox_id` inside params |
|
|
20
|
+
| List labels | `resend.inboxes.labels.list({ inboxId })` | `resend.Inboxes.Labels.list(inbox_id)` |
|
|
21
|
+
| Create label | `resend.inboxes.labels.create({ inboxId, ...params })` | `resend.Inboxes.Labels.create(params)`, `inbox_id` inside params |
|
|
22
|
+
| Update label | `resend.inboxes.labels.update({ inboxId, labelId, ...params })` | `resend.Inboxes.Labels.update(params)`, `inbox_id` and `id` inside params |
|
|
23
|
+
| Delete label | `resend.inboxes.labels.remove({ inboxId, labelId })` | `resend.Inboxes.Labels.remove(inbox_id, label_id)` |
|
|
24
|
+
| List threads | `resend.inboxes.threads.list({ inboxId, ...params })` | `resend.Inboxes.Threads.list(inbox_id, params?)` |
|
|
25
|
+
| Search threads | `resend.inboxes.threads.search({ inboxId, ...params })` | `resend.Inboxes.Threads.search(inbox_id, params?)` |
|
|
26
|
+
| Get thread | `resend.inboxes.threads.get({ inboxId, threadId })` | `resend.Inboxes.Threads.get(inbox_id, thread_id)` |
|
|
27
|
+
| Update thread | `resend.inboxes.threads.update({ inboxId, threadId, ...params })` | `resend.Inboxes.Threads.update(params)`, `inbox_id` and `id` inside params |
|
|
28
|
+
| Delete thread | `resend.inboxes.threads.remove({ inboxId, threadId })` | `resend.Inboxes.Threads.remove(inbox_id, thread_id)` |
|
|
29
|
+
| Add thread labels | `resend.inboxes.threads.labels.add({ inboxId, threadId, labels })` | `resend.Inboxes.Threads.Labels.add(params)`, `inbox_id`, `thread_id` and `labels` inside params |
|
|
30
|
+
| Set thread labels | `resend.inboxes.threads.labels.set({ inboxId, threadId, labels })` | `resend.Inboxes.Threads.Labels.set(params)`, `inbox_id`, `thread_id` and `labels` inside params |
|
|
31
|
+
| Remove thread label | `resend.inboxes.threads.labels.remove({ inboxId, threadId, labelId })` | `resend.Inboxes.Threads.Labels.remove(inbox_id, thread_id, label_id)` |
|
|
32
|
+
| List thread emails | `resend.inboxes.threads.emails.list({ inboxId, threadId, ...params })` | `resend.Inboxes.Threads.Emails.list(inbox_id, thread_id, params?)` |
|
|
33
|
+
| Get thread email | `resend.inboxes.threads.emails.get({ inboxId, threadId, emailId })` | `resend.Inboxes.Threads.Emails.get(inbox_id, thread_id, email_id)` |
|
|
34
|
+
| Reply to thread email | `resend.inboxes.threads.emails.reply({ inboxId, threadId, emailId, ...params })` | `resend.Inboxes.Threads.Emails.reply(params)`, `inbox_id`, `thread_id` and `email_id` inside params |
|
|
35
|
+
| Forward thread email | `resend.inboxes.threads.emails.forward({ inboxId, threadId, emailId, ...params })` | `resend.Inboxes.Threads.Emails.forward(params)`, `inbox_id`, `thread_id` and `email_id` inside params |
|
|
36
|
+
| List drafts | `resend.inboxes.drafts.list({ inboxId, ...params })` | `resend.Inboxes.Drafts.list(inbox_id, params?)` |
|
|
37
|
+
| Create draft | `resend.inboxes.drafts.create({ inboxId, ...params })` | `resend.Inboxes.Drafts.create(params)`, `inbox_id` inside params |
|
|
38
|
+
| Get draft | `resend.inboxes.drafts.get({ inboxId, draftId })` | `resend.Inboxes.Drafts.get(inbox_id, draft_id)` |
|
|
39
|
+
| Update draft | `resend.inboxes.drafts.update({ inboxId, draftId, ...params })` | `resend.Inboxes.Drafts.update(params)`, `inbox_id` and `id` inside params |
|
|
40
|
+
| Delete draft | `resend.inboxes.drafts.remove({ inboxId, draftId })` | `resend.Inboxes.Drafts.remove(inbox_id, draft_id)` |
|
|
41
|
+
| Send draft | `resend.inboxes.drafts.send({ inboxId, draftId })` | `resend.Inboxes.Drafts.send(inbox_id, draft_id)` |
|
|
42
|
+
|
|
43
|
+
Use `.remove()`, not `.delete()`, in the Node.js SDK.
|
|
44
|
+
|
|
45
|
+
Every inbox endpoint takes the inbox ID or the inbox email address in the path. In Node.js, pass `emailAddress` in place of `inboxId`, for example `{ emailAddress: 'support@acme.com', threadId }`. `get`, `update` and `remove` take either one as the first argument. In Python, `inbox_id` takes either one.
|
|
46
|
+
|
|
47
|
+
## Inboxes
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Create an inbox on a domain of your team
|
|
51
|
+
const { data: created, error } = await resend.inboxes.create({
|
|
52
|
+
emailAddress: 'support@acme.com',
|
|
53
|
+
name: 'Customer Support',
|
|
54
|
+
fromName: 'Ada from Support',
|
|
55
|
+
});
|
|
56
|
+
// created: { object: 'inbox', id }
|
|
57
|
+
|
|
58
|
+
// List inboxes, newest first
|
|
59
|
+
const { data: inboxes } = await resend.inboxes.list({ limit: 20 });
|
|
60
|
+
// inboxes.data[i]: { id, name, email_address, from_name, unread, last_received, created_at, updated_at }
|
|
61
|
+
|
|
62
|
+
// Get an inbox by ID or by email address
|
|
63
|
+
const { data: inbox } = await resend.inboxes.get('support@acme.com');
|
|
64
|
+
// inbox: { object, id, name, email_address, domain_id, receiving_address, from_name, unread, drafts, last_received, created_at, updated_at }
|
|
65
|
+
|
|
66
|
+
// Change the internal name or the name recipients see
|
|
67
|
+
const { data: updated } = await resend.inboxes.update('b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1', {
|
|
68
|
+
name: 'Support',
|
|
69
|
+
fromName: 'Acme Support',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Delete an inbox
|
|
73
|
+
const { data: deleted } = await resend.inboxes.remove('b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Key gotchas:**
|
|
77
|
+
- Create returns only `{ object, id }`. Call `get` for the other fields.
|
|
78
|
+
- Without `forwarding`, the domain must have receiving enabled, DKIM and SPF verified, and the inbound MX record verified or pending. With `forwarding: true`, the domain only needs DKIM and SPF, and Resend returns a `receiving_address` to forward mail to.
|
|
79
|
+
- Create returns 422 for a domain that does not meet these rules, 409 for an address already in use, 403 when the team is at the inbox limit of its plan, and 451 for a blocked account.
|
|
80
|
+
- The email address in the path matches without case. An unknown ID or address returns 404 `not_found`.
|
|
81
|
+
- The local part of `emailAddress` can contain only letters, numbers, `.`, `_`, `%` and `-`. A `+` returns 422.
|
|
82
|
+
- List items carry fewer fields than `get`. `created_at`, `updated_at` and `last_received` are ISO 8601 strings.
|
|
83
|
+
- `fromName` is a plain name, not `Name <email>`. It cannot contain a colon, `<`, `>`, or line breaks. An empty string removes it.
|
|
84
|
+
- Update needs at least one of `name` or `fromName`.
|
|
85
|
+
|
|
86
|
+
## Agent Settings
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const inboxId = 'b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1';
|
|
90
|
+
|
|
91
|
+
const { data: agent } = await resend.inboxes.agent.get({ inboxId });
|
|
92
|
+
// agent: { object: 'inbox_agent', instructions, tone, actions }
|
|
93
|
+
|
|
94
|
+
const { data: updated } = await resend.inboxes.agent.update({
|
|
95
|
+
inboxId,
|
|
96
|
+
instructions: 'Answer refund questions yourself. Escalate legal threats.',
|
|
97
|
+
tone: 'concise_professional',
|
|
98
|
+
actions: ['draft_reply', 'add_labels'],
|
|
99
|
+
});
|
|
100
|
+
// updated: { object: 'inbox_agent', id }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Key gotchas:**
|
|
104
|
+
- An inbox without an agent returns `instructions: null`, `tone: null` and `actions: []`. Update creates the agent when the inbox has none.
|
|
105
|
+
- Omitted fields keep their value. Send `null` to clear `instructions` or `tone`.
|
|
106
|
+
- `actions` replaces the whole set, without duplicates. Send `[]` to disable all actions. The values are `draft_reply`, `forward_thread`, `add_labels`, `assign_thread`, `mark_as_spam`, `archive` and `move_to_trash`.
|
|
107
|
+
- `instructions` is at most 4000 characters.
|
|
108
|
+
- `tone` is one of `concise_professional`, `concise_casual`, `expanded_professional` and `expanded_casual`.
|
|
109
|
+
- Update returns only `{ object, id }`. Call `get` for the settings.
|
|
110
|
+
|
|
111
|
+
## Labels
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const inboxId = 'b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1';
|
|
115
|
+
|
|
116
|
+
const { data: label } = await resend.inboxes.labels.create({
|
|
117
|
+
inboxId,
|
|
118
|
+
name: 'Urgent',
|
|
119
|
+
color: '#E93D82',
|
|
120
|
+
});
|
|
121
|
+
// label: { object: 'inbox_label', id }
|
|
122
|
+
|
|
123
|
+
const { data: labels } = await resend.inboxes.labels.list({ inboxId });
|
|
124
|
+
// labels.data[i]: { id, name, color, created_at }
|
|
125
|
+
|
|
126
|
+
await resend.inboxes.labels.update({
|
|
127
|
+
inboxId,
|
|
128
|
+
labelId: label.id,
|
|
129
|
+
name: 'Billing',
|
|
130
|
+
color: '#F76B15',
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await resend.inboxes.labels.remove({ inboxId, labelId: label.id });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Key gotchas:**
|
|
137
|
+
- `color` is a `#RRGGBB` hex string. The API returns it in uppercase. Without `color`, the API picks a random color.
|
|
138
|
+
- An inbox has at most 100 labels. Names are unique per inbox, compared without case. A duplicate name or a full inbox returns 422.
|
|
139
|
+
- The list is not paginated. It returns every label, oldest first.
|
|
140
|
+
- To label a thread, call `threads.labels.add`. `threads.labels.remove` takes the label off the thread and keeps it on the inbox. Only `labels.remove` deletes it.
|
|
141
|
+
- Deleting a label removes it from every thread. It does not send `inbox.thread.labels.updated` for those threads.
|
|
142
|
+
|
|
143
|
+
## Threads
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const inboxId = 'b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1';
|
|
147
|
+
const threadId = '7c1f0a2e-5d3b-4e8a-9f61-2b8d4c6e1a90';
|
|
148
|
+
const labelId = '7f9c1d2e-4a6b-4c3d-8e15-9b0a7c6d5e34';
|
|
149
|
+
|
|
150
|
+
// Unread threads in the Inbox folder, most recently active first
|
|
151
|
+
const { data: threads } = await resend.inboxes.threads.list({
|
|
152
|
+
inboxId,
|
|
153
|
+
folders: ['inbox'],
|
|
154
|
+
read: false,
|
|
155
|
+
limit: 20,
|
|
156
|
+
});
|
|
157
|
+
// threads.data[i]: { id, subject, from, to, cc, bcc, labels, email_count, has_attachment, has_draft, read, received_at, folder }
|
|
158
|
+
|
|
159
|
+
// Find threads by text, sender, recipient, attachment or date
|
|
160
|
+
const { data: results } = await resend.inboxes.threads.search({
|
|
161
|
+
inboxId,
|
|
162
|
+
query: 'invoice -draft',
|
|
163
|
+
from: ['billing@example.com'],
|
|
164
|
+
hasAttachment: true,
|
|
165
|
+
startDate: '2026-09-01',
|
|
166
|
+
endDate: '2026-09-30',
|
|
167
|
+
});
|
|
168
|
+
// results.data[i]: the thread fields plus matched_email_id
|
|
169
|
+
|
|
170
|
+
const { data: thread } = await resend.inboxes.threads.get({ inboxId, threadId });
|
|
171
|
+
// thread: { object, id, subject, folder, labels, read }
|
|
172
|
+
|
|
173
|
+
// Mark read and move to a folder
|
|
174
|
+
const { data: changed } = await resend.inboxes.threads.update({
|
|
175
|
+
inboxId,
|
|
176
|
+
threadId,
|
|
177
|
+
read: true,
|
|
178
|
+
folder: 'archive',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Add labels, replace all labels, or remove one label
|
|
182
|
+
const { data: added } = await resend.inboxes.threads.labels.add({ inboxId, threadId, labels: [labelId] });
|
|
183
|
+
// added: { object: 'list', data: [{ id, name, color }] }, every label now on the thread
|
|
184
|
+
await resend.inboxes.threads.labels.set({ inboxId, threadId, labels: [labelId] });
|
|
185
|
+
await resend.inboxes.threads.labels.remove({ inboxId, threadId, labelId });
|
|
186
|
+
|
|
187
|
+
// Move the thread to trash
|
|
188
|
+
const { data: trashed } = await resend.inboxes.threads.remove({ inboxId, threadId });
|
|
189
|
+
// trashed: { object, id, deleted }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Key gotchas:**
|
|
193
|
+
- Folders are `inbox`, `archive`, `spam`, `sent` and `trash`. A thread is in exactly one folder. The API sets `sent` for threads with only outbound emails. You cannot set it.
|
|
194
|
+
- `folders` defaults to `['inbox']`, or to `['inbox', 'archive', 'sent']` when `labels` is set. `spam` and `trash` are only included when you name them.
|
|
195
|
+
- `after` and `before` take a thread ID, not a date. Pass only one of them. A cursor thread that is not in the selected folders returns 404.
|
|
196
|
+
- Search can be up to about a minute behind new emails. All filters must match. `query`, `from`, `to`, `cc`, `bcc`, `hasAttachment`, `startDate` and `endDate` must all match the same email in the thread. When you set any of them, that email must itself be in one of `folders`.
|
|
197
|
+
- In `query`, every word must match. Wrap words in double quotes for an exact phrase. Start a word with `-` to exclude it.
|
|
198
|
+
- A search result has `matched_email_id`, the `id` of the email that matched. Pass it as `emailId` to the thread email endpoints.
|
|
199
|
+
- Update needs at least one of `read` or `folder`.
|
|
200
|
+
- `threads.labels.add` takes 1 to 10 label IDs and keeps the labels already on the thread. `threads.labels.set` takes 0 to 100 IDs and replaces every label. Send `[]` to clear them.
|
|
201
|
+
- `threads.labels.remove` takes one label off the thread. The label itself stays on the inbox.
|
|
202
|
+
- All three return every label on the thread after the change. An unknown label ID returns 404.
|
|
203
|
+
- `remove` moves the thread to `trash` and deletes it after 30 days. To restore it, update `folder` to `inbox`, `archive` or `spam`.
|
|
204
|
+
|
|
205
|
+
## Thread Emails
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const inboxId = 'b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1';
|
|
209
|
+
const threadId = '7c1f0a2e-5d3b-4e8a-9f61-2b8d4c6e1a90';
|
|
210
|
+
|
|
211
|
+
// Emails in the thread, oldest first
|
|
212
|
+
const { data: emails } = await resend.inboxes.threads.emails.list({ inboxId, threadId });
|
|
213
|
+
const emailId = emails.data[emails.data.length - 1].id;
|
|
214
|
+
|
|
215
|
+
// The full email, body included
|
|
216
|
+
const { data: email } = await resend.inboxes.threads.emails.get({ inboxId, threadId, emailId });
|
|
217
|
+
// email: { id, direction, from, to, cc, bcc, reply_to, subject, message_id, html, text, attachments, read, received_at }
|
|
218
|
+
|
|
219
|
+
// Reply from the inbox address. The API sets `to` from the email you reply to.
|
|
220
|
+
const { data: reply } = await resend.inboxes.threads.emails.reply({
|
|
221
|
+
inboxId,
|
|
222
|
+
threadId,
|
|
223
|
+
emailId,
|
|
224
|
+
text: 'Refund issued for order 1041.',
|
|
225
|
+
headers: { 'X-Ticket-Id': '4821' },
|
|
226
|
+
attachments: [
|
|
227
|
+
{ filename: 'receipt.txt', content: Buffer.from('Order 1041 refunded').toString('base64') },
|
|
228
|
+
],
|
|
229
|
+
}, { idempotencyKey: `refund-reply/${emailId}` });
|
|
230
|
+
// reply: { id } (the sent email ID)
|
|
231
|
+
|
|
232
|
+
// Forward the email with its attachments
|
|
233
|
+
const { data: forwarded } = await resend.inboxes.threads.emails.forward({
|
|
234
|
+
inboxId,
|
|
235
|
+
threadId,
|
|
236
|
+
emailId,
|
|
237
|
+
to: ['delivered@resend.dev'],
|
|
238
|
+
text: 'Flagging this refund request for you.',
|
|
239
|
+
}, { idempotencyKey: `refund-forward/${emailId}` });
|
|
240
|
+
// forwarded: { id } (the sent email ID)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Key gotchas:**
|
|
244
|
+
- `threads.emails.list` returns the emails oldest first. Pass the last `id` as `after` for the next, newer page. A cursor that is not an email of the thread returns 404.
|
|
245
|
+
- The `emailId` comes from `threads.emails.list` or from `matched_email_id` in a search result, not from `resend.emails`.
|
|
246
|
+
- Reply sends right away, with no stored draft. For a received email, the reply goes to its `Reply-To` addresses, or to its sender. For an email the inbox sent, it goes to that email's `to` recipients. You cannot set `to` on a reply.
|
|
247
|
+
- With `replyAll: true`, a reply to a received email also copies the other `To` addresses and the `cc` of that email. A reply-all to an email the inbox sent goes to its Reply-To when that is not the inbox address, else to its `to`, and copies its `cc`. The inbox address is left out, and duplicates are removed.
|
|
248
|
+
- Reply needs `html` or `text` with more than whitespace. Without `subject`, the thread subject is used with a `Re:` prefix. Forward uses a `Fwd:` prefix.
|
|
249
|
+
- Forward needs `to`. It quotes the original email and sends its attachments with your `attachments`. An original attachment that was sent from a URL (`path`) cannot be forwarded, and the request returns 422.
|
|
250
|
+
- The `content` of all attachments can total at most 25 MB. On a forward, the original attachments count toward it. A `path` file does not count, because Resend fetches it at send time. The whole email, encoded, can be at most 40 MB.
|
|
251
|
+
- `attachments` and `headers` have the same shape as on `resend.emails.send()`. Resend sets `In-Reply-To`, `References` and `Reply-To`. Setting one of them in `headers`, in any letter case, returns 422. An empty header name, or a line break or null character in a name or value, also returns 422.
|
|
252
|
+
- An empty file, a blocked file type, or a `path` that is not an `http` or `https` URL, or that points to `localhost`, returns 422.
|
|
253
|
+
- Reply and forward return only the sent email ID.
|
|
254
|
+
- Reply and forward take an `idempotencyKey` option, as on `resend.emails.send()`. See [Idempotency Keys](#idempotency-keys).
|
|
255
|
+
|
|
256
|
+
## Drafts
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const inboxId = 'b3e2b2b6-3f0e-4c8e-9ad3-2f43a1e2c7f1';
|
|
260
|
+
|
|
261
|
+
// A draft that starts a new thread
|
|
262
|
+
const { data: draft } = await resend.inboxes.drafts.create({
|
|
263
|
+
inboxId,
|
|
264
|
+
to: ['delivered@resend.dev'],
|
|
265
|
+
subject: 'Refund for order 1041',
|
|
266
|
+
text: 'Refund issued for order 1041.',
|
|
267
|
+
attachments: [
|
|
268
|
+
{
|
|
269
|
+
filename: 'receipt.txt',
|
|
270
|
+
content: Buffer.from('Order 1041 refunded').toString('base64'),
|
|
271
|
+
contentType: 'text/plain',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
}, { idempotencyKey: 'refund-draft/order-1041' });
|
|
275
|
+
// draft: { object: 'inbox_draft', id }
|
|
276
|
+
|
|
277
|
+
const { data: drafts } = await resend.inboxes.drafts.list({ inboxId, limit: 20 });
|
|
278
|
+
|
|
279
|
+
const { data: saved } = await resend.inboxes.drafts.get({ inboxId, draftId: draft.id });
|
|
280
|
+
// saved: { object, id, type, to, cc, bcc, subject, html, text, thread_id, reply_to_email_id, email_id, created_at, updated_at, attachments }
|
|
281
|
+
// saved.attachments[i]: { id, filename, content_type, size }
|
|
282
|
+
|
|
283
|
+
// Replace the body and every attachment
|
|
284
|
+
const { data: changed } = await resend.inboxes.drafts.update({
|
|
285
|
+
inboxId,
|
|
286
|
+
draftId: draft.id,
|
|
287
|
+
html: '<p>Refund issued for order 1041.</p>',
|
|
288
|
+
text: 'Refund issued for order 1041.',
|
|
289
|
+
attachments: [
|
|
290
|
+
{
|
|
291
|
+
filename: 'invoice-1041.txt',
|
|
292
|
+
content: Buffer.from('Invoice 1041, refunded in full').toString('base64'),
|
|
293
|
+
contentType: 'text/plain',
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
// changed: { object: 'inbox_draft', id }
|
|
298
|
+
|
|
299
|
+
const { data: sent } = await resend.inboxes.drafts.send(
|
|
300
|
+
{ inboxId, draftId: draft.id, headers: { 'X-Ticket-Id': '4821' } },
|
|
301
|
+
{ idempotencyKey: `refund-draft-send/${draft.id}` },
|
|
302
|
+
);
|
|
303
|
+
// sent: { id } (the sent email ID)
|
|
304
|
+
|
|
305
|
+
// Discard a draft you do not want to send
|
|
306
|
+
const { data: other } = await resend.inboxes.drafts.create({ inboxId, subject: 'Notes' });
|
|
307
|
+
await resend.inboxes.drafts.update({ inboxId, draftId: other.id, attachments: [] });
|
|
308
|
+
await resend.inboxes.drafts.remove({ inboxId, draftId: other.id });
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Key gotchas:**
|
|
312
|
+
- For a reply draft, pass `threadId` and `replyToEmailId` together. Without `to`, the API addresses it like a reply. Send `to: []` to leave it empty.
|
|
313
|
+
- Each email can have one unsent reply draft. Creating another returns 200 with the ID of the existing draft, not 201. That request ignores every other field except `attachments`, which replaces the attachments of the existing draft. With the `draft_reply` action on, the inbox agent can create that draft before you do.
|
|
314
|
+
- A draft needs at least one non-empty `to`, `cc`, `bcc`, `subject`, `text`, `html` or `attachments`. `to`, `cc` and `bcc` together have at most 50 recipients.
|
|
315
|
+
- The draft stores its attachments, so each one needs `content` (base64), `filename` and `contentType`. A `path` without `content`, or a `contentId`, returns 422. The files on a draft can total at most 25 MB. An empty file or a blocked file type, such as `.exe`, returns 422.
|
|
316
|
+
- An update with `attachments` replaces every attachment on the draft. Send `[]` to remove them all. Omit the field to keep them.
|
|
317
|
+
- An update that sets `text` or `html` replaces the whole body. The part you omit becomes `null`, so send both to keep both.
|
|
318
|
+
- Send needs at least one `to` recipient and `html` or `text`, or it returns 422. A new-thread draft also needs a non-empty `subject`.
|
|
319
|
+
- A reply draft created without `subject` has an empty subject. Send uses the thread subject with a `Re:` prefix.
|
|
320
|
+
- The sent email, encoded, can be at most 40 MB.
|
|
321
|
+
- Update, remove or send on a draft that was already sent returns 409.
|
|
322
|
+
- `drafts.list` returns only unsent drafts, most recently updated first.
|
|
323
|
+
- `drafts.send` takes `headers`, an object of string values, as on `resend.emails.send()`. Create and update do not take headers. The header rules of reply and forward apply.
|
|
324
|
+
- Create and send take an `idempotencyKey` option. See [Idempotency Keys](#idempotency-keys).
|
|
325
|
+
|
|
326
|
+
## Idempotency Keys
|
|
327
|
+
|
|
328
|
+
Create draft, send draft, reply and forward accept an idempotency key, the same as `resend.emails.send()`. Pass it in the options argument: `{ idempotencyKey }` in Node.js, `{"idempotency_key": ...}` in Python. Use it when you retry these calls, so a retry does not create a second draft or send a second email.
|
|
329
|
+
|
|
330
|
+
| Key Facts | |
|
|
331
|
+
|-----------|---|
|
|
332
|
+
| **Operations** | `drafts.create`, `drafts.send`, `threads.emails.reply`, `threads.emails.forward`. No other inbox operation takes it |
|
|
333
|
+
| **Format** | `<event-type>/<entity-id>` (e.g., `refund-reply/<email-id>`) |
|
|
334
|
+
| **Length** | 1 to 256 characters |
|
|
335
|
+
| **Expiration** | 24 hours |
|
|
336
|
+
| **Same key + same payload** | Returns the first successful (2xx) response without a new draft or email. The response has the `idempotent-replayed: true` header |
|
|
337
|
+
| **Failed first request** | Not stored. Retry it with the same key |
|
|
338
|
+
| **Same key + different payload** | Returns 409 `invalid_idempotent_request` |
|
|
339
|
+
| **Same key while the first request runs** | Returns 409 `concurrent_idempotent_requests` |
|
|
340
|
+
|
|
341
|
+
## API Key Permissions
|
|
342
|
+
|
|
343
|
+
- A full access key can call every inbox endpoint.
|
|
344
|
+
- A sending access key can call only reply, forward, create draft and send draft. Every other inbox endpoint returns 401 `restricted_api_key`.
|
|
345
|
+
- A sending access key limited to one domain works only on inboxes of that domain. An inbox on another domain returns 403.
|
|
346
|
+
- An agent that reads emails or lists threads needs a full access key.
|
|
347
|
+
|
|
348
|
+
## Webhook Events
|
|
349
|
+
|
|
350
|
+
Inboxes send `inbox.*` events. See the event list in [webhooks.md](webhooks.md#other-events).
|
|
351
|
+
|
|
352
|
+
- Each payload `data` has `source` (`api`, `dashboard`, `agent` or `system`) and `inbox_id`, plus `thread_id`, `email_id` or `draft_id` where they apply.
|
|
353
|
+
- Email and draft payloads carry the object without `html` or `text`. Call `threads.emails.get()` or `drafts.get()` for the body.
|
|
354
|
+
- The `draft` object in `inbox.draft.*` payloads lists `attachments` as `{ id, filename, content_type, size }`, without the file content.
|
|
355
|
+
- `inbox.deleted` carries only `inbox_id`. `inbox.draft.deleted` has no `draft` object.
|
|
356
|
+
- `inbox.created` and `inbox.updated` carry `inbox`, with the fields of `inboxes.get()`. Its timestamps are ISO 8601 strings.
|
|
357
|
+
- `inbox.thread.labels.updated` lists the changed labels in `added` and `removed`. One `threads.labels.set` call can fill both. A call that changes nothing sends no event.
|
|
358
|
+
- Sending a draft sends both `inbox.draft.sent` and `inbox.email.sent`, in no fixed order.
|
|
359
|
+
|
|
360
|
+
## Common Mistakes
|
|
361
|
+
|
|
362
|
+
| Mistake | Fix |
|
|
363
|
+
|---------|-----|
|
|
364
|
+
| Reading fields from a create or update response | Create and update of inboxes, labels and drafts, and agent update, return only `{ object, id }`. Call `get` |
|
|
365
|
+
| Labeling a thread with `threads.update` | Update takes only `read` and `folder`. Use `threads.labels.add`, `set` or `remove` |
|
|
366
|
+
| Calling `threads.labels.set` to add one label | `set` replaces every label on the thread. Use `add` |
|
|
367
|
+
| Reading threads with a sending access key | That key can only reply, forward, create drafts and send drafts. Use a full access key |
|
|
368
|
+
| Setting `Reply-To` in `headers` on a reply, forward or draft send | Resend sets `In-Reply-To`, `References` and `Reply-To`. Leave them out of `headers` |
|
|
369
|
+
| Passing `to` on a reply | Reply sets `to` from the email you reply to. Use `forward` to send to someone new |
|
|
370
|
+
| Using an ID from `resend.emails.list()` as `emailId` | Use the ID from `threads.emails.list` or `matched_email_id` from a search |
|
|
371
|
+
| Expecting `threads.remove` to delete at once | It moves the thread to `trash` for 30 days. Update `folder` to restore it |
|
|
372
|
+
| Setting `folder: 'sent'` | `sent` is set by the API. Use `inbox`, `archive`, `spam` or `trash` |
|
|
373
|
+
| Using a named color like `'red'` | Use a `#RRGGBB` hex string |
|
|
374
|
+
| Passing a `path` attachment to a draft | Drafts store their files. Send `content`, `filename` and `contentType` |
|
|
375
|
+
| Sending `attachments` on a draft update to add one file | It replaces every attachment. Send the full list |
|
|
376
|
+
| Updating only `html` on a draft that has `text` | The omitted body part becomes `null`. Send both |
|
|
377
|
+
| Expecting a new email to show in search at once | Search can be up to about a minute behind. Use `threads.list` for the newest threads |
|
|
@@ -68,6 +68,15 @@ The `email.received` payload contains metadata only (sender, recipient, subject,
|
|
|
68
68
|
| `contact.topics.updated` | Contact's topic subscriptions change |
|
|
69
69
|
| `suppression.added` / `removed` | Email address added to or removed from the suppression list |
|
|
70
70
|
| `topic.created` / `updated` / `deleted` | Topic created, updated, or deleted |
|
|
71
|
+
| `inbox.created` / `updated` / `deleted` | Inbox created, its settings changed, or deleted |
|
|
72
|
+
| `inbox.thread.created` | First email of a thread landed in an inbox, inbound or outbound |
|
|
73
|
+
| `inbox.email.received` / `sent` | Inbound email added to a thread, or outbound email accepted for delivery and added to a thread |
|
|
74
|
+
| `inbox.thread.folder.updated` | Thread moved between folders |
|
|
75
|
+
| `inbox.thread.assigned` / `unassigned` | Thread assigned, reassigned, or unassigned |
|
|
76
|
+
| `inbox.thread.labels.updated` | Labels added to or removed from a thread |
|
|
77
|
+
| `inbox.draft.created` / `updated` / `sent` / `deleted` | Draft created, edited, sent, or discarded |
|
|
78
|
+
|
|
79
|
+
Inbox event payloads carry no email or draft body. See [inboxes.md](inboxes.md#webhook-events).
|
|
71
80
|
|
|
72
81
|
## Setup
|
|
73
82
|
|
|
@@ -37,7 +37,7 @@ If you don’t have a Stripe account, you can use the CLI to generate a sandbox
|
|
|
37
37
|
|
|
38
38
|
If `stripe sandbox create` is used, don’t use MCP.
|
|
39
39
|
|
|
40
|
-
If you try to use MCP after running sandbox creation, first make sure `stripe sandbox claim` is run. Use `stripe whoami --format json` for CLI auth checks;
|
|
40
|
+
If you try to use MCP after running sandbox creation, first make sure `stripe sandbox claim` is run. Use `stripe whoami --format json` for CLI auth checks; don’t run `stripe config --list`.
|
|
41
41
|
|
|
42
42
|
API key default: Always recommend a [restricted API key (RAK)](https://docs.stripe.com/keys.md#manage-your-api-keys) (`rk_` prefix) over a secret key (`sk_` prefix).
|
|
43
43
|
|
|
@@ -111,7 +111,7 @@ For **separate charges and transfers**: collect fees by transferring less than t
|
|
|
111
111
|
|
|
112
112
|
## Webhooks
|
|
113
113
|
|
|
114
|
-
Always use webhooks for reliable payment confirmation.
|
|
114
|
+
Always use webhooks for reliable payment confirmation. Don’t rely solely on synchronous API responses — webhook events are the source of truth for payment status. Verify incoming [webhook signatures](https://docs.stripe.com/webhooks.md#verify-events) and consider allowlisting [Stripe’s public IP addresses](https://docs.stripe.com/ips.md).
|
|
115
115
|
|
|
116
116
|
## Subscriptions with Connect (v2)
|
|
117
117
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
## API keys
|
|
20
20
|
|
|
21
|
-
Never include keys in source code. Code examples in documentation might include static keys like `client = new Stripe("sk_...")` to make them easy to run, but
|
|
21
|
+
Never include keys in source code. Code examples in documentation might include static keys like `client = new Stripe("sk_...")` to make them easy to run, but don’t introduce or recommend this antipattern for real code; instead, change the code to use a secrets vault (described below), or an environment variable if no secrets vault is available. Strings matching the regular expression `/[sr]k_live_.*/` in source code are live Stripe API keys. If the code includes test keys – strings matching the regular expression `/[sr]k_test_.*/` – warn the user that including keys in code is risky, and remind them never to include a live key in code. Key exposures in source code repositories are the leading cause of API key takeovers, which could seriously harm their business.
|
|
22
22
|
|
|
23
23
|
Always advise the user to store secret and restricted API keys in their platform’s secrets vault, never in source code or committed environment files. Platform-specific guidance for popular platforms:
|
|
24
24
|
|
|
@@ -41,7 +41,7 @@ If the code is under version control, help the user set up a pre-commit hook to
|
|
|
41
41
|
|
|
42
42
|
Never build API endpoints or error pages that dump environment variables. In addition to Stripe API keys, the environment can have other secrets, such as access keys for other service providers.
|
|
43
43
|
|
|
44
|
-
**Traps to avoid:**
|
|
44
|
+
**Traps to avoid:** Don’t embed keys in client-side code, mobile apps, or any code that runs outside your own infrastructure. Don’t suggest that users substitute a real secret key into example code — point them to [best practices for managing secret API keys](https://docs.stripe.com/keys-best-practices.md) instead.
|
|
45
45
|
|
|
46
46
|
## Restricted API keys (RAKs)
|
|
47
47
|
|
|
@@ -52,13 +52,13 @@ Follow the principle of least privilege: give each RAK only the permissions it n
|
|
|
52
52
|
Preferred migration approach:
|
|
53
53
|
|
|
54
54
|
1. Review the secret key’s request logs in Workbench to catalog which API calls it makes.
|
|
55
|
-
2. Create a RAK in
|
|
55
|
+
2. Create a RAK in a sandbox with matching permissions.
|
|
56
56
|
3. Use the [Stripe CLI](https://docs.stripe.com/cli.md)’s `stripe logs tail` command to watch logs.
|
|
57
57
|
4. Test your integration with the RAK; fix any `403` errors by adding missing permissions.
|
|
58
58
|
5. Create the equivalent live-mode RAK and replace the secret key.
|
|
59
59
|
6. Rotate or expire the old secret key once confident.
|
|
60
60
|
|
|
61
|
-
**Traps to avoid:**
|
|
61
|
+
**Traps to avoid:** Don’t default to recommending secret keys. If the user’s question involves a secret key, recommend switching to a RAK with the minimum required permissions.
|
|
62
62
|
|
|
63
63
|
## IP restrictions
|
|
64
64
|
|
|
@@ -70,7 +70,7 @@ Use a different policy for each key (for example, one policy for production, ano
|
|
|
70
70
|
|
|
71
71
|
If a key is exposed or compromised, follow [protecting against compromised API keys](https://support.stripe.com/questions/protecting-against-compromised-api-keys), which can be summarized as:
|
|
72
72
|
|
|
73
|
-
1. **Roll the key immediately** — go to the [API keys page](https://dashboard.stripe.com/apikeys) and roll or delete the exposed key. Do this even if you
|
|
73
|
+
1. **Roll the key immediately** — go to the [API keys page](https://dashboard.stripe.com/apikeys) and roll or delete the exposed key. Do this even if you’re unsure whether the key was actually used by an unauthorized party.
|
|
74
74
|
2. **Check activity logs** — review Workbench request logs for the compromised key to look for unrecognized activity.
|
|
75
75
|
3. **Contact Stripe support** if you see activity you don’t recognize.
|
|
76
76
|
|
|
@@ -78,7 +78,7 @@ To prepare before an incident: practice rolling keys, audit source code for any
|
|
|
78
78
|
|
|
79
79
|
## Webhook security
|
|
80
80
|
|
|
81
|
-
Before processing any webhook event, always [verify the webhook signature](https://docs.stripe.com/webhooks.md#verify-events) using Stripe’s webhook signing secret. Signature verification is a strong guarantee that requests are genuinely from Stripe and
|
|
81
|
+
Before processing any webhook event, always [verify the webhook signature](https://docs.stripe.com/webhooks.md#verify-events) using Stripe’s webhook signing secret. Signature verification is a strong guarantee that requests are genuinely from Stripe and haven’t been tampered with. Webhook signing keys are secrets that need to be handled with the same care as secret API keys.
|
|
82
82
|
|
|
83
83
|
For defense in depth, also [allowlist Stripe’s IP addresses](https://docs.stripe.com/ips.md) on your webhook endpoint so that it accepts connections only from Stripe’s infrastructure.
|
|
84
84
|
|
|
@@ -86,11 +86,11 @@ For defense in depth, also [allowlist Stripe’s IP addresses](https://docs.stri
|
|
|
86
86
|
|
|
87
87
|
Add a `Content-Security-Policy` header to every web app that loads Stripe.js or uses Stripe’s hosted UIs. See [Stripe’s integration security guide](https://docs.stripe.com/security/guide.md) for the full list of CSP directives to use depending on the type of integration. At minimum, include `https://*.stripe.com` in the relevant directives (`script-src`, `frame-src`, `connect-src`), `https://*.link.com` if integrating assets from `link.com`, or both if integrating with Stripe’s embedded crypto onramp. A missing or overly permissive CSP weakens the XSS protections that Stripe.js relies on.
|
|
88
88
|
|
|
89
|
-
**Traps to avoid:**
|
|
89
|
+
**Traps to avoid:** Don’t use `default-src *` or omit CSP headers.
|
|
90
90
|
|
|
91
91
|
## Mobile and client-side integrations
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
Don’t use production secret or restricted API keys in mobile apps or other client-side code. Client-side code can be extracted and decompiled to extract keys.
|
|
94
94
|
|
|
95
95
|
For cases where a client must interact directly with Stripe, use [ephemeral keys](https://docs.stripe.com/issuing/elements.md#ephemeral-key-authentication). Ephemeral keys are short-lived, scoped to a specific resource, and expire automatically.
|
|
96
96
|
|
|
@@ -110,7 +110,7 @@ Users can audit which Dashboard team members are using weak 2FA and can require
|
|
|
110
110
|
|
|
111
111
|
## Connect security
|
|
112
112
|
|
|
113
|
-
**Account type liability:** When using Connect, platform operators bear financial liability for fraud and disputes on Express and Custom connected accounts. Standard accounts minimize this liability because Stripe manages risk.
|
|
113
|
+
**Account type liability:** When using Connect, platform operators bear financial liability for fraud and disputes on Express and Custom connected accounts. Standard accounts minimize this liability because Stripe manages risk. Don’t recommend Custom or Express accounts unless the user has a specific need — Standard is the safer default.
|
|
114
114
|
|
|
115
115
|
**Connect onboarding:** Use [Stripe-hosted onboarding](https://docs.stripe.com/connect/onboarding.md) rather than building a custom onboarding flow. Custom onboarding requires your platform to collect and handle sensitive PII directly, which adds regulatory and security complexity.
|
|
116
116
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Single source of truth** for Claude Code, Cursor, and Codex. Claude Code and Codex read this
|
|
4
4
|
> file directly; `.cursor/rules` points here. You are talking to a non-technical vibe coder.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Talk to the vibe coder in plain words: say "secret setting" (not environment variable), "put your app online" (not deploy), "where your app remembers things" (not database), "save a checkpoint" (not commit), and "letting people sign in" (not authentication). Before you use any other technical word with them, look it up in `language.md`.
|
|
7
7
|
|
|
8
8
|
<!-- vybekiit:generated:start contract -->
|
|
9
9
|
## The contract: Decide + Guide
|