feat(broadcasts): add duplicate command
This adds resend broadcasts duplicate <id> on top of resend.broadcasts.duplicate (POST /broadcasts/{id}/duplicate, spec in https://github.com/resend/resend-openapi/pull/115). The copy is a draft named <name> (copy) with the same segment, topic, from, subject, reply-to, preview text, and content; any broadcast can be duplicated, including sent ones. It picks from every broadcast like get does and follows templates duplicate (runCreate, create_error). The CLI releases as 2.21.0.
It depends on resend@6.28.0, which is not on npm yet; the method lands in https://github.com/resend/resend-node/pull/1094. The resend pin is already 6.28.0 but the lockfile follows the Node release, so pnpm install --frozen-lockfile stays red and this PR stays draft until 6.28.0 is published and the lockfile commit lands here; merging before that breaks install for every contributor and CI. Locally, checks and the live calls ran against a pack of that branch.
Live calls against staging through the built CLI. First a draft source, then a sent one; each copy is inspected and deleted:
$ node dist/cli.cjs broadcasts duplicate e6bd3843-3cc7-4c0d-b7fd-e01cbb514a16 --json
{
"object": "broadcast",
"id": "cedfd557-a42c-4182-9ee6-b0ec7afcafb2"
}
$ node dist/cli.cjs broadcasts get cedfd557-a42c-4182-9ee6-b0ec7afcafb2 --json | node -e 'const b=JSON.parse(require("fs").readFileSync(0,"utf8")); console.log(JSON.stringify({id:b.id,name:b.name,status:b.status,segment_id:b.segment_id,from:b.from,subject:b.subject,reply_to:b.reply_to,preview_text:b.preview_text,html_length:(b.html||"").length}))'
{"id":"cedfd557-a42c-4182-9ee6-b0ec7afcafb2","name":"Untitled (copy)","status":"draft","segment_id":"7ce6a143-b076-4291-80c3-445042a4b7d9","from":"Test <test@resend.cloud>","subject":"Another day another test","reply_to":null,"preview_text":null,"html_length":1776}
$ node dist/cli.cjs broadcasts delete cedfd557-a42c-4182-9ee6-b0ec7afcafb2 --yes
{
"object": "broadcast",
"id": "cedfd557-a42c-4182-9ee6-b0ec7afcafb2",
"deleted": true
}
$ node dist/cli.cjs broadcasts get ca3652ea-09be-4e3e-8caa-52da40702c36 --json | jq '{id, name, status, sent_at}'
{
"id": "ca3652ea-09be-4e3e-8caa-52da40702c36",
"name": "Untitled",
"status": "sent",
"sent_at": "2026-09-10 17:31:08.00087+00"
}
$ node dist/cli.cjs broadcasts duplicate ca3652ea-09be-4e3e-8caa-52da40702c36 --json
{
"object": "broadcast",
"id": "6ccb36ec-1833-4df4-8eea-e86a9efc6dd3"
}
$ node dist/cli.cjs broadcasts get 6ccb36ec-1833-4df4-8eea-e86a9efc6dd3 --json | jq '{id, name, status, segment_id, from, subject, reply_to, preview_text, html: (.html | length)}'
{
"id": "6ccb36ec-1833-4df4-8eea-e86a9efc6dd3",
"name": "Untitled (copy)",
"status": "draft",
"segment_id": "6f31db6f-41f8-4ee9-9ead-c70124822534",
"from": "Test <test@resend.cloud>",
"subject": "Why is an unsubscribe link important when sending marketing emails?",
"reply_to": null,
"preview_text": null,
"html": 4722
}
$ node dist/cli.cjs broadcasts delete 6ccb36ec-1833-4df4-8eea-e86a9efc6dd3 --yes
{
"object": "broadcast",
"id": "6ccb36ec-1833-4df4-8eea-e86a9efc6dd3",
"deleted": true
}
š¤ Generated with Claude Code