resend-cli 1.2.2 → 1.3.1

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.
Files changed (182) hide show
  1. package/README.md +25 -10
  2. package/dist/cli.cjs +562 -0
  3. package/package.json +31 -12
  4. package/.claude/settings.local.json +0 -5
  5. package/.github/scripts/pr-title-check.js +0 -34
  6. package/.github/workflows/ci.yml +0 -32
  7. package/.github/workflows/pr-title-check.yml +0 -13
  8. package/.github/workflows/release.yml +0 -120
  9. package/.github/workflows/test-install-windows.yml +0 -48
  10. package/CHANGELOG.md +0 -31
  11. package/biome.json +0 -36
  12. package/bun.lock +0 -73
  13. package/bunfig.toml +0 -2
  14. package/docs/agent-dx-gaps.md +0 -167
  15. package/docs/missing-commands.md +0 -58
  16. package/docs/production-readiness.md +0 -99
  17. package/docs/secure-key-storage.md +0 -174
  18. package/install.ps1 +0 -141
  19. package/install.sh +0 -301
  20. package/renovate.json +0 -4
  21. package/src/cli.ts +0 -92
  22. package/src/commands/api-keys/create.ts +0 -114
  23. package/src/commands/api-keys/delete.ts +0 -47
  24. package/src/commands/api-keys/index.ts +0 -26
  25. package/src/commands/api-keys/list.ts +0 -35
  26. package/src/commands/api-keys/utils.ts +0 -8
  27. package/src/commands/auth/index.ts +0 -20
  28. package/src/commands/auth/login.ts +0 -234
  29. package/src/commands/auth/logout.ts +0 -105
  30. package/src/commands/broadcasts/create.ts +0 -196
  31. package/src/commands/broadcasts/delete.ts +0 -46
  32. package/src/commands/broadcasts/get.ts +0 -59
  33. package/src/commands/broadcasts/index.ts +0 -43
  34. package/src/commands/broadcasts/list.ts +0 -60
  35. package/src/commands/broadcasts/send.ts +0 -56
  36. package/src/commands/broadcasts/update.ts +0 -95
  37. package/src/commands/broadcasts/utils.ts +0 -35
  38. package/src/commands/contact-properties/create.ts +0 -118
  39. package/src/commands/contact-properties/delete.ts +0 -48
  40. package/src/commands/contact-properties/get.ts +0 -46
  41. package/src/commands/contact-properties/index.ts +0 -48
  42. package/src/commands/contact-properties/list.ts +0 -68
  43. package/src/commands/contact-properties/update.ts +0 -88
  44. package/src/commands/contact-properties/utils.ts +0 -17
  45. package/src/commands/contacts/add-segment.ts +0 -78
  46. package/src/commands/contacts/create.ts +0 -122
  47. package/src/commands/contacts/delete.ts +0 -49
  48. package/src/commands/contacts/get.ts +0 -53
  49. package/src/commands/contacts/index.ts +0 -58
  50. package/src/commands/contacts/list.ts +0 -57
  51. package/src/commands/contacts/remove-segment.ts +0 -48
  52. package/src/commands/contacts/segments.ts +0 -39
  53. package/src/commands/contacts/topics.ts +0 -45
  54. package/src/commands/contacts/update-topics.ts +0 -90
  55. package/src/commands/contacts/update.ts +0 -77
  56. package/src/commands/contacts/utils.ts +0 -119
  57. package/src/commands/doctor.ts +0 -216
  58. package/src/commands/domains/create.ts +0 -83
  59. package/src/commands/domains/delete.ts +0 -42
  60. package/src/commands/domains/get.ts +0 -47
  61. package/src/commands/domains/index.ts +0 -35
  62. package/src/commands/domains/list.ts +0 -53
  63. package/src/commands/domains/update.ts +0 -75
  64. package/src/commands/domains/utils.ts +0 -44
  65. package/src/commands/domains/verify.ts +0 -38
  66. package/src/commands/emails/batch.ts +0 -140
  67. package/src/commands/emails/index.ts +0 -24
  68. package/src/commands/emails/receiving/attachment.ts +0 -55
  69. package/src/commands/emails/receiving/attachments.ts +0 -68
  70. package/src/commands/emails/receiving/get.ts +0 -58
  71. package/src/commands/emails/receiving/index.ts +0 -28
  72. package/src/commands/emails/receiving/list.ts +0 -59
  73. package/src/commands/emails/receiving/utils.ts +0 -38
  74. package/src/commands/emails/send.ts +0 -189
  75. package/src/commands/open.ts +0 -24
  76. package/src/commands/segments/create.ts +0 -50
  77. package/src/commands/segments/delete.ts +0 -47
  78. package/src/commands/segments/get.ts +0 -38
  79. package/src/commands/segments/index.ts +0 -36
  80. package/src/commands/segments/list.ts +0 -58
  81. package/src/commands/segments/utils.ts +0 -7
  82. package/src/commands/teams/index.ts +0 -10
  83. package/src/commands/teams/list.ts +0 -35
  84. package/src/commands/teams/remove.ts +0 -86
  85. package/src/commands/teams/switch.ts +0 -76
  86. package/src/commands/topics/create.ts +0 -73
  87. package/src/commands/topics/delete.ts +0 -47
  88. package/src/commands/topics/get.ts +0 -42
  89. package/src/commands/topics/index.ts +0 -42
  90. package/src/commands/topics/list.ts +0 -34
  91. package/src/commands/topics/update.ts +0 -59
  92. package/src/commands/topics/utils.ts +0 -16
  93. package/src/commands/webhooks/create.ts +0 -128
  94. package/src/commands/webhooks/delete.ts +0 -49
  95. package/src/commands/webhooks/get.ts +0 -42
  96. package/src/commands/webhooks/index.ts +0 -44
  97. package/src/commands/webhooks/list.ts +0 -55
  98. package/src/commands/webhooks/update.ts +0 -83
  99. package/src/commands/webhooks/utils.ts +0 -36
  100. package/src/commands/whoami.ts +0 -71
  101. package/src/lib/actions.ts +0 -157
  102. package/src/lib/client.ts +0 -37
  103. package/src/lib/config.ts +0 -217
  104. package/src/lib/files.ts +0 -15
  105. package/src/lib/help-text.ts +0 -38
  106. package/src/lib/output.ts +0 -54
  107. package/src/lib/pagination.ts +0 -36
  108. package/src/lib/prompts.ts +0 -149
  109. package/src/lib/spinner.ts +0 -100
  110. package/src/lib/table.ts +0 -57
  111. package/src/lib/tty.ts +0 -28
  112. package/src/lib/update-check.ts +0 -172
  113. package/src/lib/version.ts +0 -4
  114. package/tests/commands/api-keys/create.test.ts +0 -195
  115. package/tests/commands/api-keys/delete.test.ts +0 -156
  116. package/tests/commands/api-keys/list.test.ts +0 -133
  117. package/tests/commands/auth/login.test.ts +0 -156
  118. package/tests/commands/auth/logout.test.ts +0 -146
  119. package/tests/commands/broadcasts/create.test.ts +0 -447
  120. package/tests/commands/broadcasts/delete.test.ts +0 -182
  121. package/tests/commands/broadcasts/get.test.ts +0 -146
  122. package/tests/commands/broadcasts/list.test.ts +0 -196
  123. package/tests/commands/broadcasts/send.test.ts +0 -161
  124. package/tests/commands/broadcasts/update.test.ts +0 -283
  125. package/tests/commands/contact-properties/create.test.ts +0 -250
  126. package/tests/commands/contact-properties/delete.test.ts +0 -183
  127. package/tests/commands/contact-properties/get.test.ts +0 -144
  128. package/tests/commands/contact-properties/list.test.ts +0 -180
  129. package/tests/commands/contact-properties/update.test.ts +0 -216
  130. package/tests/commands/contacts/add-segment.test.ts +0 -188
  131. package/tests/commands/contacts/create.test.ts +0 -270
  132. package/tests/commands/contacts/delete.test.ts +0 -192
  133. package/tests/commands/contacts/get.test.ts +0 -148
  134. package/tests/commands/contacts/list.test.ts +0 -175
  135. package/tests/commands/contacts/remove-segment.test.ts +0 -166
  136. package/tests/commands/contacts/segments.test.ts +0 -167
  137. package/tests/commands/contacts/topics.test.ts +0 -163
  138. package/tests/commands/contacts/update-topics.test.ts +0 -247
  139. package/tests/commands/contacts/update.test.ts +0 -205
  140. package/tests/commands/doctor.test.ts +0 -165
  141. package/tests/commands/domains/create.test.ts +0 -192
  142. package/tests/commands/domains/delete.test.ts +0 -156
  143. package/tests/commands/domains/get.test.ts +0 -137
  144. package/tests/commands/domains/list.test.ts +0 -164
  145. package/tests/commands/domains/update.test.ts +0 -223
  146. package/tests/commands/domains/verify.test.ts +0 -117
  147. package/tests/commands/emails/batch.test.ts +0 -313
  148. package/tests/commands/emails/receiving/attachment.test.ts +0 -140
  149. package/tests/commands/emails/receiving/attachments.test.ts +0 -168
  150. package/tests/commands/emails/receiving/get.test.ts +0 -140
  151. package/tests/commands/emails/receiving/list.test.ts +0 -181
  152. package/tests/commands/emails/send.test.ts +0 -309
  153. package/tests/commands/segments/create.test.ts +0 -163
  154. package/tests/commands/segments/delete.test.ts +0 -182
  155. package/tests/commands/segments/get.test.ts +0 -137
  156. package/tests/commands/segments/list.test.ts +0 -173
  157. package/tests/commands/teams/list.test.ts +0 -63
  158. package/tests/commands/teams/remove.test.ts +0 -103
  159. package/tests/commands/teams/switch.test.ts +0 -96
  160. package/tests/commands/topics/create.test.ts +0 -191
  161. package/tests/commands/topics/delete.test.ts +0 -156
  162. package/tests/commands/topics/get.test.ts +0 -125
  163. package/tests/commands/topics/list.test.ts +0 -124
  164. package/tests/commands/topics/update.test.ts +0 -177
  165. package/tests/commands/webhooks/create.test.ts +0 -224
  166. package/tests/commands/webhooks/delete.test.ts +0 -156
  167. package/tests/commands/webhooks/get.test.ts +0 -125
  168. package/tests/commands/webhooks/list.test.ts +0 -177
  169. package/tests/commands/webhooks/update.test.ts +0 -206
  170. package/tests/commands/whoami.test.ts +0 -99
  171. package/tests/helpers.ts +0 -93
  172. package/tests/lib/client.test.ts +0 -71
  173. package/tests/lib/config.test.ts +0 -445
  174. package/tests/lib/files.test.ts +0 -65
  175. package/tests/lib/help-text.test.ts +0 -97
  176. package/tests/lib/output.test.ts +0 -127
  177. package/tests/lib/prompts.test.ts +0 -178
  178. package/tests/lib/spinner.test.ts +0 -146
  179. package/tests/lib/table.test.ts +0 -63
  180. package/tests/lib/tty.test.ts +0 -85
  181. package/tests/lib/update-check.test.ts +0 -169
  182. package/tsconfig.json +0 -14
@@ -1,189 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import { Command } from '@commander-js/extra-typings';
3
- import type { Resend } from 'resend';
4
- import type { GlobalOpts } from '../../lib/client';
5
- import { requireClient } from '../../lib/client';
6
- import { readFile } from '../../lib/files';
7
- import { buildHelpText } from '../../lib/help-text';
8
- import { outputResult } from '../../lib/output';
9
- import {
10
- cancelAndExit,
11
- promptForMissing,
12
- requireText,
13
- } from '../../lib/prompts';
14
- import { withSpinner } from '../../lib/spinner';
15
- import { isInteractive } from '../../lib/tty';
16
-
17
- export async function fetchVerifiedDomains(resend: Resend): Promise<string[]> {
18
- try {
19
- const { data, error } = await resend.domains.list();
20
- if (error || !data) {
21
- return [];
22
- }
23
- return data.data
24
- .filter(
25
- (d) => d.status === 'verified' && d.capabilities.sending === 'enabled',
26
- )
27
- .map((d) => d.name);
28
- } catch {
29
- return [];
30
- }
31
- }
32
-
33
- const FROM_PREFIXES = ['noreply', 'hello', 'hi', 'info', 'support', 'team'];
34
-
35
- async function promptForFromAddress(domains: string[]): Promise<string> {
36
- let domain: string;
37
- if (domains.length === 1) {
38
- domain = domains[0];
39
- } else {
40
- const result = await p.select({
41
- message: 'Select a verified domain',
42
- options: domains.map((d) => ({ value: d, label: d })),
43
- });
44
- if (p.isCancel(result)) {
45
- cancelAndExit('Send cancelled.');
46
- }
47
- domain = result;
48
- }
49
-
50
- const options: Array<{ value: string | null; label: string }> =
51
- FROM_PREFIXES.map((prefix) => ({
52
- value: `${prefix}@${domain}`,
53
- label: `${prefix}@${domain}`,
54
- }));
55
- options.push({ value: null, label: 'Custom address...' });
56
-
57
- const result = await p.select({
58
- message: `From address (@${domain})`,
59
- options,
60
- });
61
- if (p.isCancel(result)) {
62
- cancelAndExit('Send cancelled.');
63
- }
64
-
65
- if (result === null) {
66
- const custom = await p.text({
67
- message: 'From address',
68
- placeholder: `you@${domain}`,
69
- validate: (v) =>
70
- !v || !v.includes('@') ? 'Enter a valid email address' : undefined,
71
- });
72
- if (p.isCancel(custom)) {
73
- cancelAndExit('Send cancelled.');
74
- }
75
- return custom;
76
- }
77
-
78
- return result;
79
- }
80
-
81
- export const sendCommand = new Command('send')
82
- .description('Send an email')
83
- .option('--from <address>', 'Sender address (required)')
84
- .option('--to <addresses...>', 'Recipient address(es) (required)')
85
- .option('--subject <subject>', 'Email subject (required)')
86
- .option('--html <html>', 'HTML body')
87
- .option('--html-file <path>', 'Path to an HTML file for the body')
88
- .option('--text <text>', 'Plain-text body')
89
- .option('--cc <addresses...>', 'CC recipients')
90
- .option('--bcc <addresses...>', 'BCC recipients')
91
- .option('--reply-to <address>', 'Reply-to address')
92
- .addHelpText(
93
- 'after',
94
- buildHelpText({
95
- context:
96
- 'Required: --from, --to, --subject, and one of --text | --html | --html-file',
97
- output: ' {"id":"<email-id>"}',
98
- errorCodes: ['auth_error', 'missing_body', 'send_error'],
99
- examples: [
100
- 'resend emails send --from you@domain.com --to user@example.com --subject "Hello" --text "Hi"',
101
- 'resend emails send --from you@domain.com --to a@example.com --to b@example.com --subject "Hi" --html "<b>Hi</b>" --json',
102
- 'resend emails send --from you@domain.com --to user@example.com --subject "Hi" --html-file ./email.html --json',
103
- 'RESEND_API_KEY=re_123 resend emails send --from you@domain.com --to user@example.com --subject "Hi" --text "Hi"',
104
- ],
105
- }),
106
- )
107
- .action(async (opts, cmd) => {
108
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
109
-
110
- const resend = requireClient(globalOpts);
111
-
112
- // Only fetch verified domains in interactive mode — non-interactive
113
- // callers (CI, agents, scripts) must pass --from explicitly.
114
- let fromAddress = opts.from;
115
- if (!fromAddress && isInteractive()) {
116
- const domains = await fetchVerifiedDomains(resend);
117
- if (domains.length > 0) {
118
- fromAddress = await promptForFromAddress(domains);
119
- }
120
- }
121
-
122
- const filled = await promptForMissing(
123
- { from: fromAddress, to: opts.to?.[0], subject: opts.subject },
124
- [
125
- {
126
- flag: 'from',
127
- message: 'From address',
128
- placeholder: 'you@example.com',
129
- },
130
- {
131
- flag: 'to',
132
- message: 'To address',
133
- placeholder: 'recipient@example.com',
134
- },
135
- { flag: 'subject', message: 'Subject', placeholder: 'Hello!' },
136
- ],
137
- globalOpts,
138
- );
139
-
140
- let html = opts.html;
141
- const text = opts.text;
142
-
143
- if (opts.htmlFile) {
144
- html = readFile(opts.htmlFile, globalOpts);
145
- }
146
-
147
- let body: string | undefined = text;
148
- if (!html && !text) {
149
- body = await requireText(
150
- undefined,
151
- {
152
- message: 'Email body (plain text)',
153
- placeholder: 'Type your message...',
154
- },
155
- {
156
- message: 'Missing email body. Provide --html, --html-file, or --text',
157
- code: 'missing_body',
158
- },
159
- globalOpts,
160
- );
161
- }
162
-
163
- const toAddresses = opts.to ?? [filled.to];
164
-
165
- const data = await withSpinner(
166
- {
167
- loading: 'Sending email...',
168
- success: 'Email sent',
169
- fail: 'Failed to send email',
170
- },
171
- () =>
172
- resend.emails.send({
173
- from: filled.from,
174
- to: toAddresses,
175
- subject: filled.subject,
176
- ...(html ? { html } : { text: body as string }),
177
- ...(opts.cc && { cc: opts.cc }),
178
- ...(opts.bcc && { bcc: opts.bcc }),
179
- ...(opts.replyTo && { replyTo: opts.replyTo }),
180
- }),
181
- 'send_error',
182
- globalOpts,
183
- );
184
- if (!globalOpts.json && isInteractive()) {
185
- console.log(`\nEmail sent: ${data.id}`);
186
- } else {
187
- outputResult(data, { json: globalOpts.json });
188
- }
189
- });
@@ -1,24 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { buildHelpText } from '../lib/help-text';
3
-
4
- export const openCommand = new Command('open')
5
- .description('Open the Resend dashboard in your browser')
6
- .addHelpText(
7
- 'after',
8
- buildHelpText({
9
- context: 'Opens https://resend.com/emails in your default browser.',
10
- examples: ['resend open'],
11
- }),
12
- )
13
- .action(async () => {
14
- const url = 'https://resend.com/emails';
15
- const { platform } = process;
16
- const args =
17
- platform === 'darwin'
18
- ? ['open', url]
19
- : platform === 'win32'
20
- ? ['cmd', '/c', 'start', url]
21
- : ['xdg-open', url];
22
-
23
- Bun.spawn(args, { stdio: ['ignore', 'ignore', 'ignore'] });
24
- });
@@ -1,50 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runCreate } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import { requireText } from '../../lib/prompts';
6
-
7
- export const createSegmentCommand = new Command('create')
8
- .description('Create a new segment')
9
- .option('--name <name>', 'Segment name (required)')
10
- .addHelpText(
11
- 'after',
12
- buildHelpText({
13
- context: `Segments are named groups of contacts. Broadcasts target segments via segment_id.
14
- Contacts can belong to multiple segments. Audiences are deprecated — use segments instead.
15
-
16
- Non-interactive: --name is required.`,
17
- output: ` {"object":"segment","id":"<uuid>","name":"<name>"}`,
18
- errorCodes: ['auth_error', 'missing_name', 'create_error'],
19
- examples: [
20
- 'resend segments create --name "Newsletter Subscribers"',
21
- 'resend segments create --name "Beta Users" --json',
22
- ],
23
- }),
24
- )
25
- .action(async (opts, cmd) => {
26
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
27
-
28
- const name = await requireText(
29
- opts.name,
30
- { message: 'Segment name', placeholder: 'Newsletter Subscribers' },
31
- { message: 'Missing --name flag.', code: 'missing_name' },
32
- globalOpts,
33
- );
34
-
35
- await runCreate(
36
- {
37
- spinner: {
38
- loading: 'Creating segment...',
39
- success: 'Segment created',
40
- fail: 'Failed to create segment',
41
- },
42
- sdkCall: (resend) => resend.segments.create({ name }),
43
- onInteractive: (data) => {
44
- console.log(`\nSegment created: ${data.id}`);
45
- console.log(`Name: ${data.name}`);
46
- },
47
- },
48
- globalOpts,
49
- );
50
- });
@@ -1,47 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runDelete } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
-
6
- export const deleteSegmentCommand = new Command('delete')
7
- .alias('rm')
8
- .description('Delete a segment')
9
- .argument('<id>', 'Segment UUID')
10
- .option(
11
- '--yes',
12
- 'Skip the confirmation prompt (required in non-interactive mode)',
13
- )
14
- .addHelpText(
15
- 'after',
16
- buildHelpText({
17
- context: `Warning: Deleting a segment removes it as a target for future broadcasts,
18
- but does NOT delete the contacts within it.
19
-
20
- Non-interactive: --yes is required to confirm deletion when stdin/stdout is not a TTY.`,
21
- output: ` {"object":"segment","id":"<uuid>","deleted":true}`,
22
- errorCodes: ['auth_error', 'confirmation_required', 'delete_error'],
23
- examples: [
24
- 'resend segments delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes',
25
- 'resend segments delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes --json',
26
- ],
27
- }),
28
- )
29
- .action(async (id, opts, cmd) => {
30
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
31
- await runDelete(
32
- id,
33
- !!opts.yes,
34
- {
35
- confirmMessage: `Delete segment ${id}?\nContacts will not be deleted, but broadcasts targeting this segment will no longer work.`,
36
- spinner: {
37
- loading: 'Deleting segment...',
38
- success: 'Segment deleted',
39
- fail: 'Failed to delete segment',
40
- },
41
- object: 'segment',
42
- successMsg: 'Segment deleted.',
43
- sdkCall: (resend) => resend.segments.remove(id),
44
- },
45
- globalOpts,
46
- );
47
- });
@@ -1,38 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runGet } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
-
6
- export const getSegmentCommand = new Command('get')
7
- .description('Retrieve a segment by ID')
8
- .argument('<id>', 'Segment UUID')
9
- .addHelpText(
10
- 'after',
11
- buildHelpText({
12
- output: ` {"object":"segment","id":"<uuid>","name":"<name>","created_at":"<iso-date>"}`,
13
- errorCodes: ['auth_error', 'fetch_error'],
14
- examples: [
15
- 'resend segments get 78261eea-8f8b-4381-83c6-79fa7120f1cf',
16
- 'resend segments get 78261eea-8f8b-4381-83c6-79fa7120f1cf --json',
17
- ],
18
- }),
19
- )
20
- .action(async (id, _opts, cmd) => {
21
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
22
- await runGet(
23
- {
24
- spinner: {
25
- loading: 'Fetching segment...',
26
- success: 'Segment fetched',
27
- fail: 'Failed to fetch segment',
28
- },
29
- sdkCall: (resend) => resend.segments.get(id),
30
- onInteractive: (data) => {
31
- console.log(`\n${data.name}`);
32
- console.log(`ID: ${data.id}`);
33
- console.log(`Created: ${data.created_at}`);
34
- },
35
- },
36
- globalOpts,
37
- );
38
- });
@@ -1,36 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { buildHelpText } from '../../lib/help-text';
3
- import { createSegmentCommand } from './create';
4
- import { deleteSegmentCommand } from './delete';
5
- import { getSegmentCommand } from './get';
6
- import { listSegmentsCommand } from './list';
7
-
8
- export const segmentsCommand = new Command('segments')
9
- .description(
10
- 'Manage segments — named groups of contacts used to target broadcasts',
11
- )
12
- .addHelpText(
13
- 'after',
14
- buildHelpText({
15
- context: `Segments are the modern replacement for Audiences (deprecated).
16
- A segment is a named group of contacts. Broadcasts target segments via segment_id.
17
- Contacts can belong to multiple segments.
18
-
19
- Segment membership is managed through the contacts namespace:
20
- resend contacts add-segment <contactId> --segment-id <segmentId>
21
- resend contacts remove-segment <contactId> <segmentId>
22
- resend contacts segments <contactId>
23
-
24
- There is no "update" endpoint — to rename a segment, delete it and recreate.`,
25
- examples: [
26
- 'resend segments list',
27
- 'resend segments create --name "Newsletter Subscribers"',
28
- 'resend segments get 78261eea-8f8b-4381-83c6-79fa7120f1cf',
29
- 'resend segments delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes',
30
- ],
31
- }),
32
- )
33
- .addCommand(createSegmentCommand)
34
- .addCommand(getSegmentCommand)
35
- .addCommand(listSegmentsCommand, { isDefault: true })
36
- .addCommand(deleteSegmentCommand);
@@ -1,58 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runList } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import {
6
- buildPaginationOpts,
7
- parseLimitOpt,
8
- printPaginationHint,
9
- } from '../../lib/pagination';
10
- import { renderSegmentsTable } from './utils';
11
-
12
- export const listSegmentsCommand = new Command('list')
13
- .alias('ls')
14
- .description('List all segments')
15
- .option('--limit <n>', 'Maximum number of segments to return (1-100)', '10')
16
- .option('--after <cursor>', 'Return segments after this cursor (next page)')
17
- .option(
18
- '--before <cursor>',
19
- 'Return segments before this cursor (previous page)',
20
- )
21
- .addHelpText(
22
- 'after',
23
- buildHelpText({
24
- context: `Pagination: use --after or --before with a segment ID as the cursor.
25
- Only one of --after or --before may be used at a time.
26
- The response includes has_more: true when additional pages exist.
27
-
28
- Use "resend segments list" to discover segment IDs for use with broadcasts
29
- or "resend contacts add-segment".`,
30
- output: ` {"object":"list","data":[{"id":"<uuid>","name":"<name>","created_at":"<iso-date>"}],"has_more":false}`,
31
- errorCodes: ['auth_error', 'invalid_limit', 'list_error'],
32
- examples: [
33
- 'resend segments list',
34
- 'resend segments list --limit 25 --json',
35
- 'resend segments list --after 78261eea-8f8b-4381-83c6-79fa7120f1cf --json',
36
- ],
37
- }),
38
- )
39
- .action(async (opts, cmd) => {
40
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
41
- const limit = parseLimitOpt(opts.limit, globalOpts);
42
- const paginationOpts = buildPaginationOpts(limit, opts.after, opts.before);
43
- await runList(
44
- {
45
- spinner: {
46
- loading: 'Fetching segments...',
47
- success: 'Segments fetched',
48
- fail: 'Failed to list segments',
49
- },
50
- sdkCall: (resend) => resend.segments.list(paginationOpts),
51
- onInteractive: (list) => {
52
- console.log(renderSegmentsTable(list.data));
53
- printPaginationHint(list);
54
- },
55
- },
56
- globalOpts,
57
- );
58
- });
@@ -1,7 +0,0 @@
1
- import type { Segment } from 'resend';
2
- import { renderTable } from '../../lib/table';
3
-
4
- export function renderSegmentsTable(segments: Segment[]): string {
5
- const rows = segments.map((s) => [s.name, s.id, s.created_at]);
6
- return renderTable(['Name', 'ID', 'Created'], rows, '(no segments)');
7
- }
@@ -1,10 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { listCommand } from './list';
3
- import { removeCommand } from './remove';
4
- import { switchCommand } from './switch';
5
-
6
- export const teamsCommand = new Command('teams')
7
- .description('Manage team profiles for multiple API keys')
8
- .addCommand(listCommand)
9
- .addCommand(switchCommand)
10
- .addCommand(removeCommand);
@@ -1,35 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import type { GlobalOpts } from '../../lib/client';
3
- import { listTeams } from '../../lib/config';
4
- import { outputResult } from '../../lib/output';
5
- import { isInteractive } from '../../lib/tty';
6
-
7
- export const listCommand = new Command('list')
8
- .description('List all team profiles')
9
- .action((_opts, cmd) => {
10
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
11
- const teams = listTeams();
12
-
13
- if (globalOpts.json) {
14
- outputResult({ teams }, { json: true });
15
- return;
16
- }
17
-
18
- if (teams.length === 0) {
19
- console.log('No teams configured. Run: resend login');
20
- return;
21
- }
22
-
23
- if (isInteractive()) {
24
- console.log('\n Teams\n');
25
- }
26
-
27
- for (const team of teams) {
28
- const marker = team.active ? ' (active)' : '';
29
- console.log(` ${team.active ? '▸' : ' '} ${team.name}${marker}`);
30
- }
31
-
32
- if (isInteractive()) {
33
- console.log('');
34
- }
35
- });
@@ -1,86 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import { Command } from '@commander-js/extra-typings';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { listTeams, removeApiKey } from '../../lib/config';
5
- import { errorMessage, outputError, outputResult } from '../../lib/output';
6
- import { cancelAndExit } from '../../lib/prompts';
7
- import { isInteractive } from '../../lib/tty';
8
-
9
- export const removeCommand = new Command('remove')
10
- .description('Remove a team profile')
11
- .argument('[name]', 'Team name to remove')
12
- .action(async (name, _opts, cmd) => {
13
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
14
-
15
- let teamName = name;
16
-
17
- if (!teamName) {
18
- if (!isInteractive()) {
19
- outputError(
20
- {
21
- message:
22
- 'Missing team name. Provide a team name in non-interactive mode.',
23
- code: 'missing_name',
24
- },
25
- { json: globalOpts.json },
26
- );
27
- return;
28
- }
29
-
30
- const teams = listTeams();
31
- if (teams.length === 0) {
32
- outputError(
33
- {
34
- message: 'No teams configured. Run `resend login` first.',
35
- code: 'no_teams',
36
- },
37
- { json: globalOpts.json },
38
- );
39
- return;
40
- }
41
-
42
- const choice = await p.select({
43
- message: 'Remove which team?',
44
- options: teams.map((t) => ({
45
- value: t.name,
46
- label: t.name,
47
- hint: t.active ? 'active' : undefined,
48
- })),
49
- });
50
-
51
- if (p.isCancel(choice)) {
52
- cancelAndExit('Remove cancelled.');
53
- }
54
-
55
- teamName = choice;
56
- }
57
-
58
- if (!globalOpts.json && isInteractive()) {
59
- const confirmed = await p.confirm({
60
- message: `Remove team '${teamName}' and its API key?`,
61
- });
62
-
63
- if (p.isCancel(confirmed) || !confirmed) {
64
- cancelAndExit('Remove cancelled.');
65
- }
66
- }
67
-
68
- try {
69
- removeApiKey(teamName);
70
- } catch (err) {
71
- outputError(
72
- {
73
- message: errorMessage(err, 'Failed to remove team'),
74
- code: 'remove_failed',
75
- },
76
- { json: globalOpts.json },
77
- );
78
- return;
79
- }
80
-
81
- if (globalOpts.json) {
82
- outputResult({ success: true, removed_team: teamName }, { json: true });
83
- } else if (isInteractive()) {
84
- console.log(`Team '${teamName}' removed.`);
85
- }
86
- });
@@ -1,76 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import { Command } from '@commander-js/extra-typings';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { listTeams, setActiveTeam } from '../../lib/config';
5
- import { errorMessage, outputError, outputResult } from '../../lib/output';
6
- import { cancelAndExit } from '../../lib/prompts';
7
- import { isInteractive } from '../../lib/tty';
8
-
9
- export const switchCommand = new Command('switch')
10
- .description('Switch the active team profile')
11
- .argument('[name]', 'Team name to switch to')
12
- .action(async (name, _opts, cmd) => {
13
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
14
-
15
- let teamName = name;
16
-
17
- if (!teamName) {
18
- if (!isInteractive()) {
19
- outputError(
20
- {
21
- message:
22
- 'Missing team name. Provide a team name in non-interactive mode.',
23
- code: 'missing_name',
24
- },
25
- { json: globalOpts.json },
26
- );
27
- return;
28
- }
29
-
30
- const teams = listTeams();
31
- if (teams.length === 0) {
32
- outputError(
33
- {
34
- message: 'No teams configured. Run `resend login` first.',
35
- code: 'no_teams',
36
- },
37
- { json: globalOpts.json },
38
- );
39
- return;
40
- }
41
-
42
- const choice = await p.select({
43
- message: 'Switch to which team?',
44
- options: teams.map((t) => ({
45
- value: t.name,
46
- label: t.name,
47
- hint: t.active ? 'active' : undefined,
48
- })),
49
- });
50
-
51
- if (p.isCancel(choice)) {
52
- cancelAndExit('Switch cancelled.');
53
- }
54
-
55
- teamName = choice;
56
- }
57
-
58
- try {
59
- setActiveTeam(teamName);
60
- } catch (err) {
61
- outputError(
62
- {
63
- message: errorMessage(err, 'Failed to switch team'),
64
- code: 'switch_failed',
65
- },
66
- { json: globalOpts.json },
67
- );
68
- return;
69
- }
70
-
71
- if (globalOpts.json) {
72
- outputResult({ success: true, active_team: teamName }, { json: true });
73
- } else if (isInteractive()) {
74
- console.log(`Switched to team '${teamName}'.`);
75
- }
76
- });