@@ -10,42 +10,6 @@ import { buildReactEmailHtml } from '../../lib/react-email';
1010import { withSpinner } from '../../lib/spinner' ;
1111import { isInteractive } from '../../lib/tty' ;
1212
13- /** Map API snake_case fields in batch JSON to SDK camelCase before send. */
14- function normalizeBatchEmail (
15- email : Record < string , unknown > ,
16- ) : Record < string , unknown > {
17- const out = { ...email } ;
18-
19- if ( 'scheduled_at' in out && ! ( 'scheduledAt' in out ) ) {
20- out . scheduledAt = out . scheduled_at ;
21- }
22- if ( 'reply_to' in out && ! ( 'replyTo' in out ) ) {
23- out . replyTo = out . reply_to ;
24- }
25- if ( 'topic_id' in out && ! ( 'topicId' in out ) ) {
26- out . topicId = out . topic_id ;
27- }
28-
29- const attachments = out . attachments ;
30- if ( Array . isArray ( attachments ) ) {
31- out . attachments = attachments . map ( ( item ) => {
32- if ( item === null || typeof item !== 'object' || Array . isArray ( item ) ) {
33- return item ;
34- }
35- const attachment = { ...( item as Record < string , unknown > ) } ;
36- if ( 'content_type' in attachment && ! ( 'contentType' in attachment ) ) {
37- attachment . contentType = attachment . content_type ;
38- }
39- if ( 'content_id' in attachment && ! ( 'contentId' in attachment ) ) {
40- attachment . contentId = attachment . content_id ;
41- }
42- return attachment ;
43- } ) ;
44- }
45-
46- return out ;
47- }
48-
4913export const batchCommand = new Command ( 'batch' )
5014 . description ( 'Send up to 100 emails in a single API request from a JSON file' )
5115 . option (
@@ -70,7 +34,7 @@ export const batchCommand = new Command('batch')
7034 'after' ,
7135 buildHelpText ( {
7236 context :
73- 'Non-interactive: --file\nLimit: 100 emails per request (API hard limit — warned if exceeded)\nPer -email fields: attachments, scheduled_at, tags (and all single-send fields) \n\nFile format (--file path):\n [\n {"from":"onboarding@resend.com","to":["delivered@resend.com"],"subject":"Hello","text":"Hi"},\n {"from":"onboarding@resend.com","to":["delivered@resend.com"],"subject":"Hello","html":"<b>Hi</b>","scheduled_at":"2026-01-01T00:00:00Z"," tags":[{"name":"category","value":"welcome"}],"attachments":[{"filename":"doc.pdf","content":"<base64> "}]}\n ]' ,
37+ 'Non-interactive: --file\nLimit: 100 emails per request (API hard limit — warned if exceeded)\nUnsupported per -email fields: attachments, scheduled_at\nPer-email tags supported: [{"name":"category","value":"welcome"}] \n\nFile format (--file path):\n [\n {"from":"onboarding@resend.com","to":["delivered@resend.com"],"subject":"Hello","text":"Hi"},\n {"from":"onboarding@resend.com","to":["delivered@resend.com"],"subject":"Hello","html":"<b>Hi</b>","tags":[{"name":"category","value":"welcome"}]}\n ]' ,
7438 output : ' [{"id":"<email-id>"},{"id":"<email-id>"}]' ,
7539 errorCodes : [
7640 'auth_error' ,
@@ -156,6 +120,25 @@ export const batchCommand = new Command('batch')
156120 { json : globalOpts . json } ,
157121 ) ;
158122 }
123+
124+ if ( 'attachments' in email ) {
125+ outputError (
126+ {
127+ message : `Email at index ${ i } contains "attachments", which is not supported in batch sends.` ,
128+ code : 'batch_error' ,
129+ } ,
130+ { json : globalOpts . json } ,
131+ ) ;
132+ }
133+ if ( 'scheduled_at' in email ) {
134+ outputError (
135+ {
136+ message : `Email at index ${ i } contains "scheduled_at", which is not supported in batch sends.` ,
137+ code : 'batch_error' ,
138+ } ,
139+ { json : globalOpts . json } ,
140+ ) ;
141+ }
159142 }
160143
161144 const batchData = await withSpinner (
@@ -168,9 +151,7 @@ export const batchCommand = new Command('batch')
168151 } ) ,
169152 } ;
170153 return resend . batch . send (
171- emails . map ( ( email ) =>
172- normalizeBatchEmail ( email as Record < string , unknown > ) ,
173- ) as CreateBatchOptions ,
154+ emails as CreateBatchOptions ,
174155 Object . keys ( options ) . length > 0 ? options : undefined ,
175156 ) ;
176157 } ,
0 commit comments