resend-cli 1.2.0 → 1.2.2

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 (193) hide show
  1. package/.github/workflows/release.yml +35 -8
  2. package/README.md +12 -1
  3. package/biome.json +1 -1
  4. package/bun.lock +0 -3
  5. package/package.json +3 -4
  6. package/src/cli.ts +23 -5
  7. package/src/commands/auth/login.ts +10 -8
  8. package/src/commands/doctor.ts +30 -112
  9. package/src/lib/client.ts +3 -0
  10. package/src/lib/config.ts +2 -3
  11. package/src/lib/spinner.ts +17 -10
  12. package/src/lib/update-check.ts +172 -0
  13. package/tests/commands/auth/login.test.ts +3 -1
  14. package/tests/lib/config.test.ts +4 -6
  15. package/tests/lib/update-check.test.ts +169 -0
  16. package/.claude/worktrees/emails-list/.claude/settings.local.json +0 -5
  17. package/.claude/worktrees/emails-list/.github/scripts/pr-title-check.js +0 -34
  18. package/.claude/worktrees/emails-list/.github/workflows/ci.yml +0 -32
  19. package/.claude/worktrees/emails-list/.github/workflows/pr-title-check.yml +0 -13
  20. package/.claude/worktrees/emails-list/.github/workflows/release.yml +0 -93
  21. package/.claude/worktrees/emails-list/CHANGELOG.md +0 -31
  22. package/.claude/worktrees/emails-list/LICENSE +0 -21
  23. package/.claude/worktrees/emails-list/README.md +0 -424
  24. package/.claude/worktrees/emails-list/biome.json +0 -36
  25. package/.claude/worktrees/emails-list/bun.lock +0 -76
  26. package/.claude/worktrees/emails-list/bunfig.toml +0 -2
  27. package/.claude/worktrees/emails-list/install.ps1 +0 -140
  28. package/.claude/worktrees/emails-list/install.sh +0 -301
  29. package/.claude/worktrees/emails-list/package.json +0 -43
  30. package/.claude/worktrees/emails-list/renovate.json +0 -6
  31. package/.claude/worktrees/emails-list/src/cli.ts +0 -74
  32. package/.claude/worktrees/emails-list/src/commands/api-keys/create.ts +0 -114
  33. package/.claude/worktrees/emails-list/src/commands/api-keys/delete.ts +0 -47
  34. package/.claude/worktrees/emails-list/src/commands/api-keys/index.ts +0 -26
  35. package/.claude/worktrees/emails-list/src/commands/api-keys/list.ts +0 -35
  36. package/.claude/worktrees/emails-list/src/commands/api-keys/utils.ts +0 -8
  37. package/.claude/worktrees/emails-list/src/commands/auth/index.ts +0 -20
  38. package/.claude/worktrees/emails-list/src/commands/auth/login.ts +0 -207
  39. package/.claude/worktrees/emails-list/src/commands/auth/logout.ts +0 -105
  40. package/.claude/worktrees/emails-list/src/commands/broadcasts/create.ts +0 -196
  41. package/.claude/worktrees/emails-list/src/commands/broadcasts/delete.ts +0 -46
  42. package/.claude/worktrees/emails-list/src/commands/broadcasts/get.ts +0 -59
  43. package/.claude/worktrees/emails-list/src/commands/broadcasts/index.ts +0 -43
  44. package/.claude/worktrees/emails-list/src/commands/broadcasts/list.ts +0 -60
  45. package/.claude/worktrees/emails-list/src/commands/broadcasts/send.ts +0 -56
  46. package/.claude/worktrees/emails-list/src/commands/broadcasts/update.ts +0 -95
  47. package/.claude/worktrees/emails-list/src/commands/broadcasts/utils.ts +0 -35
  48. package/.claude/worktrees/emails-list/src/commands/contact-properties/create.ts +0 -118
  49. package/.claude/worktrees/emails-list/src/commands/contact-properties/delete.ts +0 -48
  50. package/.claude/worktrees/emails-list/src/commands/contact-properties/get.ts +0 -46
  51. package/.claude/worktrees/emails-list/src/commands/contact-properties/index.ts +0 -48
  52. package/.claude/worktrees/emails-list/src/commands/contact-properties/list.ts +0 -68
  53. package/.claude/worktrees/emails-list/src/commands/contact-properties/update.ts +0 -88
  54. package/.claude/worktrees/emails-list/src/commands/contact-properties/utils.ts +0 -17
  55. package/.claude/worktrees/emails-list/src/commands/contacts/add-segment.ts +0 -78
  56. package/.claude/worktrees/emails-list/src/commands/contacts/create.ts +0 -122
  57. package/.claude/worktrees/emails-list/src/commands/contacts/delete.ts +0 -49
  58. package/.claude/worktrees/emails-list/src/commands/contacts/get.ts +0 -53
  59. package/.claude/worktrees/emails-list/src/commands/contacts/index.ts +0 -58
  60. package/.claude/worktrees/emails-list/src/commands/contacts/list.ts +0 -57
  61. package/.claude/worktrees/emails-list/src/commands/contacts/remove-segment.ts +0 -48
  62. package/.claude/worktrees/emails-list/src/commands/contacts/segments.ts +0 -39
  63. package/.claude/worktrees/emails-list/src/commands/contacts/topics.ts +0 -45
  64. package/.claude/worktrees/emails-list/src/commands/contacts/update-topics.ts +0 -90
  65. package/.claude/worktrees/emails-list/src/commands/contacts/update.ts +0 -77
  66. package/.claude/worktrees/emails-list/src/commands/contacts/utils.ts +0 -119
  67. package/.claude/worktrees/emails-list/src/commands/doctor.ts +0 -298
  68. package/.claude/worktrees/emails-list/src/commands/domains/create.ts +0 -83
  69. package/.claude/worktrees/emails-list/src/commands/domains/delete.ts +0 -42
  70. package/.claude/worktrees/emails-list/src/commands/domains/get.ts +0 -47
  71. package/.claude/worktrees/emails-list/src/commands/domains/index.ts +0 -35
  72. package/.claude/worktrees/emails-list/src/commands/domains/list.ts +0 -53
  73. package/.claude/worktrees/emails-list/src/commands/domains/update.ts +0 -75
  74. package/.claude/worktrees/emails-list/src/commands/domains/utils.ts +0 -44
  75. package/.claude/worktrees/emails-list/src/commands/domains/verify.ts +0 -38
  76. package/.claude/worktrees/emails-list/src/commands/emails/batch.ts +0 -140
  77. package/.claude/worktrees/emails-list/src/commands/emails/index.ts +0 -28
  78. package/.claude/worktrees/emails-list/src/commands/emails/list.ts +0 -73
  79. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachment.ts +0 -55
  80. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachments.ts +0 -68
  81. package/.claude/worktrees/emails-list/src/commands/emails/receiving/get.ts +0 -58
  82. package/.claude/worktrees/emails-list/src/commands/emails/receiving/index.ts +0 -28
  83. package/.claude/worktrees/emails-list/src/commands/emails/receiving/list.ts +0 -59
  84. package/.claude/worktrees/emails-list/src/commands/emails/receiving/utils.ts +0 -38
  85. package/.claude/worktrees/emails-list/src/commands/emails/send.ts +0 -189
  86. package/.claude/worktrees/emails-list/src/commands/open.ts +0 -24
  87. package/.claude/worktrees/emails-list/src/commands/segments/create.ts +0 -50
  88. package/.claude/worktrees/emails-list/src/commands/segments/delete.ts +0 -47
  89. package/.claude/worktrees/emails-list/src/commands/segments/get.ts +0 -38
  90. package/.claude/worktrees/emails-list/src/commands/segments/index.ts +0 -36
  91. package/.claude/worktrees/emails-list/src/commands/segments/list.ts +0 -58
  92. package/.claude/worktrees/emails-list/src/commands/segments/utils.ts +0 -7
  93. package/.claude/worktrees/emails-list/src/commands/teams/index.ts +0 -10
  94. package/.claude/worktrees/emails-list/src/commands/teams/list.ts +0 -35
  95. package/.claude/worktrees/emails-list/src/commands/teams/remove.ts +0 -83
  96. package/.claude/worktrees/emails-list/src/commands/teams/switch.ts +0 -73
  97. package/.claude/worktrees/emails-list/src/commands/topics/create.ts +0 -73
  98. package/.claude/worktrees/emails-list/src/commands/topics/delete.ts +0 -47
  99. package/.claude/worktrees/emails-list/src/commands/topics/get.ts +0 -42
  100. package/.claude/worktrees/emails-list/src/commands/topics/index.ts +0 -42
  101. package/.claude/worktrees/emails-list/src/commands/topics/list.ts +0 -34
  102. package/.claude/worktrees/emails-list/src/commands/topics/update.ts +0 -59
  103. package/.claude/worktrees/emails-list/src/commands/topics/utils.ts +0 -16
  104. package/.claude/worktrees/emails-list/src/commands/webhooks/create.ts +0 -128
  105. package/.claude/worktrees/emails-list/src/commands/webhooks/delete.ts +0 -49
  106. package/.claude/worktrees/emails-list/src/commands/webhooks/get.ts +0 -42
  107. package/.claude/worktrees/emails-list/src/commands/webhooks/index.ts +0 -44
  108. package/.claude/worktrees/emails-list/src/commands/webhooks/list.ts +0 -55
  109. package/.claude/worktrees/emails-list/src/commands/webhooks/update.ts +0 -83
  110. package/.claude/worktrees/emails-list/src/commands/webhooks/utils.ts +0 -36
  111. package/.claude/worktrees/emails-list/src/commands/whoami.ts +0 -71
  112. package/.claude/worktrees/emails-list/src/lib/actions.ts +0 -157
  113. package/.claude/worktrees/emails-list/src/lib/client.ts +0 -34
  114. package/.claude/worktrees/emails-list/src/lib/config.ts +0 -211
  115. package/.claude/worktrees/emails-list/src/lib/files.ts +0 -15
  116. package/.claude/worktrees/emails-list/src/lib/help-text.ts +0 -38
  117. package/.claude/worktrees/emails-list/src/lib/output.ts +0 -54
  118. package/.claude/worktrees/emails-list/src/lib/pagination.ts +0 -36
  119. package/.claude/worktrees/emails-list/src/lib/prompts.ts +0 -149
  120. package/.claude/worktrees/emails-list/src/lib/spinner.ts +0 -93
  121. package/.claude/worktrees/emails-list/src/lib/table.ts +0 -57
  122. package/.claude/worktrees/emails-list/src/lib/tty.ts +0 -28
  123. package/.claude/worktrees/emails-list/src/lib/version.ts +0 -4
  124. package/.claude/worktrees/emails-list/tests/commands/api-keys/create.test.ts +0 -195
  125. package/.claude/worktrees/emails-list/tests/commands/api-keys/delete.test.ts +0 -156
  126. package/.claude/worktrees/emails-list/tests/commands/api-keys/list.test.ts +0 -133
  127. package/.claude/worktrees/emails-list/tests/commands/auth/login.test.ts +0 -119
  128. package/.claude/worktrees/emails-list/tests/commands/auth/logout.test.ts +0 -146
  129. package/.claude/worktrees/emails-list/tests/commands/broadcasts/create.test.ts +0 -447
  130. package/.claude/worktrees/emails-list/tests/commands/broadcasts/delete.test.ts +0 -182
  131. package/.claude/worktrees/emails-list/tests/commands/broadcasts/get.test.ts +0 -146
  132. package/.claude/worktrees/emails-list/tests/commands/broadcasts/list.test.ts +0 -196
  133. package/.claude/worktrees/emails-list/tests/commands/broadcasts/send.test.ts +0 -161
  134. package/.claude/worktrees/emails-list/tests/commands/broadcasts/update.test.ts +0 -283
  135. package/.claude/worktrees/emails-list/tests/commands/contact-properties/create.test.ts +0 -250
  136. package/.claude/worktrees/emails-list/tests/commands/contact-properties/delete.test.ts +0 -183
  137. package/.claude/worktrees/emails-list/tests/commands/contact-properties/get.test.ts +0 -144
  138. package/.claude/worktrees/emails-list/tests/commands/contact-properties/list.test.ts +0 -180
  139. package/.claude/worktrees/emails-list/tests/commands/contact-properties/update.test.ts +0 -216
  140. package/.claude/worktrees/emails-list/tests/commands/contacts/add-segment.test.ts +0 -188
  141. package/.claude/worktrees/emails-list/tests/commands/contacts/create.test.ts +0 -270
  142. package/.claude/worktrees/emails-list/tests/commands/contacts/delete.test.ts +0 -192
  143. package/.claude/worktrees/emails-list/tests/commands/contacts/get.test.ts +0 -148
  144. package/.claude/worktrees/emails-list/tests/commands/contacts/list.test.ts +0 -175
  145. package/.claude/worktrees/emails-list/tests/commands/contacts/remove-segment.test.ts +0 -166
  146. package/.claude/worktrees/emails-list/tests/commands/contacts/segments.test.ts +0 -167
  147. package/.claude/worktrees/emails-list/tests/commands/contacts/topics.test.ts +0 -163
  148. package/.claude/worktrees/emails-list/tests/commands/contacts/update-topics.test.ts +0 -247
  149. package/.claude/worktrees/emails-list/tests/commands/contacts/update.test.ts +0 -205
  150. package/.claude/worktrees/emails-list/tests/commands/doctor.test.ts +0 -165
  151. package/.claude/worktrees/emails-list/tests/commands/domains/create.test.ts +0 -192
  152. package/.claude/worktrees/emails-list/tests/commands/domains/delete.test.ts +0 -156
  153. package/.claude/worktrees/emails-list/tests/commands/domains/get.test.ts +0 -137
  154. package/.claude/worktrees/emails-list/tests/commands/domains/list.test.ts +0 -164
  155. package/.claude/worktrees/emails-list/tests/commands/domains/update.test.ts +0 -223
  156. package/.claude/worktrees/emails-list/tests/commands/domains/verify.test.ts +0 -117
  157. package/.claude/worktrees/emails-list/tests/commands/emails/batch.test.ts +0 -313
  158. package/.claude/worktrees/emails-list/tests/commands/emails/list.test.ts +0 -196
  159. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachment.test.ts +0 -140
  160. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachments.test.ts +0 -168
  161. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/get.test.ts +0 -140
  162. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/list.test.ts +0 -181
  163. package/.claude/worktrees/emails-list/tests/commands/emails/send.test.ts +0 -309
  164. package/.claude/worktrees/emails-list/tests/commands/segments/create.test.ts +0 -163
  165. package/.claude/worktrees/emails-list/tests/commands/segments/delete.test.ts +0 -182
  166. package/.claude/worktrees/emails-list/tests/commands/segments/get.test.ts +0 -137
  167. package/.claude/worktrees/emails-list/tests/commands/segments/list.test.ts +0 -173
  168. package/.claude/worktrees/emails-list/tests/commands/teams/list.test.ts +0 -63
  169. package/.claude/worktrees/emails-list/tests/commands/teams/remove.test.ts +0 -103
  170. package/.claude/worktrees/emails-list/tests/commands/teams/switch.test.ts +0 -96
  171. package/.claude/worktrees/emails-list/tests/commands/topics/create.test.ts +0 -191
  172. package/.claude/worktrees/emails-list/tests/commands/topics/delete.test.ts +0 -156
  173. package/.claude/worktrees/emails-list/tests/commands/topics/get.test.ts +0 -125
  174. package/.claude/worktrees/emails-list/tests/commands/topics/list.test.ts +0 -124
  175. package/.claude/worktrees/emails-list/tests/commands/topics/update.test.ts +0 -177
  176. package/.claude/worktrees/emails-list/tests/commands/webhooks/create.test.ts +0 -224
  177. package/.claude/worktrees/emails-list/tests/commands/webhooks/delete.test.ts +0 -156
  178. package/.claude/worktrees/emails-list/tests/commands/webhooks/get.test.ts +0 -125
  179. package/.claude/worktrees/emails-list/tests/commands/webhooks/list.test.ts +0 -177
  180. package/.claude/worktrees/emails-list/tests/commands/webhooks/update.test.ts +0 -206
  181. package/.claude/worktrees/emails-list/tests/commands/whoami.test.ts +0 -99
  182. package/.claude/worktrees/emails-list/tests/helpers.ts +0 -93
  183. package/.claude/worktrees/emails-list/tests/lib/client.test.ts +0 -71
  184. package/.claude/worktrees/emails-list/tests/lib/config.test.ts +0 -414
  185. package/.claude/worktrees/emails-list/tests/lib/files.test.ts +0 -65
  186. package/.claude/worktrees/emails-list/tests/lib/help-text.test.ts +0 -97
  187. package/.claude/worktrees/emails-list/tests/lib/output.test.ts +0 -127
  188. package/.claude/worktrees/emails-list/tests/lib/prompts.test.ts +0 -178
  189. package/.claude/worktrees/emails-list/tests/lib/spinner.test.ts +0 -146
  190. package/.claude/worktrees/emails-list/tests/lib/table.test.ts +0 -63
  191. package/.claude/worktrees/emails-list/tests/lib/tty.test.ts +0 -85
  192. package/.claude/worktrees/emails-list/tsconfig.json +0 -14
  193. package/.github/workflows/test-build-windows.yml +0 -44
@@ -1,28 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { buildHelpText } from '../../../lib/help-text';
3
- import { getAttachmentCommand } from './attachment';
4
- import { listAttachmentsCommand } from './attachments';
5
- import { getReceivingCommand } from './get';
6
- import { listReceivingCommand } from './list';
7
-
8
- export const receivingCommand = new Command('receiving')
9
- .description(
10
- 'Manage received (inbound) emails — requires domain receiving to be enabled',
11
- )
12
- .addHelpText(
13
- 'after',
14
- buildHelpText({
15
- context:
16
- 'Receiving must be enabled on the domain first:\n resend domains update <id> --receiving enabled',
17
- examples: [
18
- 'resend emails receiving list',
19
- 'resend emails receiving get <email-id>',
20
- 'resend emails receiving attachments <email-id>',
21
- 'resend emails receiving attachment <email-id> <attachment-id>',
22
- ],
23
- }),
24
- )
25
- .addCommand(listReceivingCommand, { isDefault: true })
26
- .addCommand(getReceivingCommand)
27
- .addCommand(listAttachmentsCommand)
28
- .addCommand(getAttachmentCommand);
@@ -1,59 +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 { renderReceivingEmailsTable } from './utils';
11
-
12
- export const listReceivingCommand = new Command('list')
13
- .alias('ls')
14
- .description(
15
- 'List received (inbound) emails for domains with receiving enabled',
16
- )
17
- .option('--limit <n>', 'Maximum number of emails to return (1-100)', '10')
18
- .option('--after <cursor>', 'Return emails after this cursor (next page)')
19
- .option(
20
- '--before <cursor>',
21
- 'Return emails before this cursor (previous page)',
22
- )
23
- .addHelpText(
24
- 'after',
25
- buildHelpText({
26
- context:
27
- 'Receiving must be enabled on the domain first:\n resend domains update <id> --receiving enabled\n\nPagination: use --after or --before with a received email ID as the cursor.\nOnly one of --after or --before may be used at a time.\nThe response includes has_more: true when additional pages exist.',
28
- output:
29
- ' {"object":"list","has_more":false,"data":[{"id":"<uuid>","to":["inbox@yourdomain.com"],"from":"sender@external.com","subject":"Hello","created_at":"<iso-date>","message_id":"<str>","bcc":null,"cc":null,"reply_to":null,"attachments":[]}]}',
30
- errorCodes: ['auth_error', 'invalid_limit', 'list_error'],
31
- examples: [
32
- 'resend emails receiving list',
33
- 'resend emails receiving list --limit 25 --json',
34
- 'resend emails receiving list --after <email-id> --json',
35
- ],
36
- }),
37
- )
38
- .action(async (opts, cmd) => {
39
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
40
-
41
- const limit = parseLimitOpt(opts.limit, globalOpts);
42
- const paginationOpts = buildPaginationOpts(limit, opts.after, opts.before);
43
-
44
- await runList(
45
- {
46
- spinner: {
47
- loading: 'Fetching received emails...',
48
- success: 'Received emails fetched',
49
- fail: 'Failed to list received emails',
50
- },
51
- sdkCall: (resend) => resend.emails.receiving.list(paginationOpts),
52
- onInteractive: (list) => {
53
- console.log(renderReceivingEmailsTable(list.data));
54
- printPaginationHint(list);
55
- },
56
- },
57
- globalOpts,
58
- );
59
- });
@@ -1,38 +0,0 @@
1
- import type {
2
- ListAttachmentsResponseSuccess,
3
- ListReceivingEmail,
4
- } from 'resend';
5
- import { renderTable } from '../../../lib/table';
6
-
7
- export function renderReceivingEmailsTable(
8
- emails: ListReceivingEmail[],
9
- ): string {
10
- const rows = emails.map((e) => {
11
- const to = e.to.join(', ');
12
- const toStr = to.length > 40 ? `${to.slice(0, 37)}...` : to;
13
- const subject =
14
- e.subject.length > 50 ? `${e.subject.slice(0, 47)}...` : e.subject;
15
- return [e.from, toStr, subject, e.created_at, e.id];
16
- });
17
- return renderTable(
18
- ['From', 'To', 'Subject', 'Created At', 'ID'],
19
- rows,
20
- '(no received emails)',
21
- );
22
- }
23
-
24
- export function renderAttachmentsTable(
25
- attachments: ListAttachmentsResponseSuccess['data'],
26
- ): string {
27
- const rows = attachments.map((a) => [
28
- a.filename ?? '(unnamed)',
29
- a.content_type,
30
- String(a.size),
31
- a.id,
32
- ]);
33
- return renderTable(
34
- ['Filename', 'Content-Type', 'Size (bytes)', 'ID'],
35
- rows,
36
- '(no attachments)',
37
- );
38
- }
@@ -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
- });