Skip to content

Commit 4fff8d5

Browse files
dahliaclaude
andcommitted
Implement batch processing for sendMany()
Instead of sending emails sequentially, sendMany() now batches multiple messages into a single JMAP request with multiple Email/set creates and EmailSubmission/set creates. This reduces HTTP round-trips and improves throughput when sending multiple emails. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8cd7bd3 commit 4fff8d5

File tree

4 files changed

+576
-42
lines changed

4 files changed

+576
-42
lines changed

docs/transports/jmap.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Bulk email sending
187187
------------------
188188

189189
For sending multiple emails, the JMAP transport provides efficient
190-
sequential processing with comprehensive error handling:
190+
batch processing that combines all messages into a single HTTP request:
191191

192192
~~~~ typescript twoslash
193193
import { JmapTransport } from "@upyo/jmap";
@@ -224,10 +224,10 @@ for await (const receipt of transport.sendMany(messages)) {
224224
}
225225
~~~~
226226

227-
The `~JmapTransport.sendMany()` method processes emails sequentially,
228-
yielding a receipt for each message. Failed emails don't prevent
229-
subsequent emails from being sent, and the session is efficiently
230-
reused across all messages.
227+
The `~JmapTransport.sendMany()` method batches all emails into a single
228+
JMAP request, significantly reducing HTTP round-trips. Each message
229+
gets its own receipt, and partial failures are handled gracefully—if
230+
some emails fail, others in the batch can still succeed.
231231

232232

233233
Error handling

packages/jmap/src/jmap-transport.e2e.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ describe(
230230
assert.ok(receipt.successful);
231231
});
232232

233-
it("should send multiple emails with sendMany", async () => {
233+
it("should send multiple emails with sendMany in a single batch", async () => {
234234
const config = getTestConfig();
235235
const transport = new JmapTransport(config.jmap);
236236

@@ -243,6 +243,10 @@ describe(
243243
subject: "E2E Test - Batch 2",
244244
content: { text: "Second email in batch" },
245245
}),
246+
createTestMessage({
247+
subject: "E2E Test - Batch 3",
248+
content: { text: "Third email in batch" },
249+
}),
246250
];
247251

248252
const receipts = [];
@@ -253,6 +257,69 @@ describe(
253257
receipts.push(receipt);
254258
}
255259

260+
assert.equal(receipts.length, 3);
261+
assert.ok(receipts.every((r) => r.successful));
262+
263+
// Verify each message has a unique submission ID
264+
if (receipts.every((r) => r.successful)) {
265+
const messageIds = receipts.map((r) =>
266+
r.successful ? r.messageId : null
267+
);
268+
const uniqueIds = new Set(messageIds);
269+
assert.equal(
270+
uniqueIds.size,
271+
3,
272+
"Each message should have a unique submission ID",
273+
);
274+
}
275+
});
276+
277+
it("should batch emails with attachments via sendMany", async () => {
278+
const config = getTestConfig();
279+
const transport = new JmapTransport(config.jmap);
280+
281+
const textBytes = new TextEncoder().encode("Attachment content");
282+
283+
const messages = [
284+
createTestMessage({
285+
subject: "E2E Test - Batch with Attachment 1",
286+
content: { text: "First email with attachment" },
287+
attachments: [
288+
{
289+
filename: "file1.txt",
290+
contentType: "text/plain" as const,
291+
content: textBytes,
292+
contentId: "attachment-1",
293+
inline: false,
294+
},
295+
],
296+
}),
297+
createTestMessage({
298+
subject: "E2E Test - Batch with Attachment 2",
299+
content: { text: "Second email with attachment" },
300+
attachments: [
301+
{
302+
filename: "file2.txt",
303+
contentType: "text/plain" as const,
304+
content: textBytes,
305+
contentId: "attachment-2",
306+
inline: false,
307+
},
308+
],
309+
}),
310+
];
311+
312+
const receipts = [];
313+
for await (const receipt of transport.sendMany(messages)) {
314+
if (!receipt.successful) {
315+
console.error(
316+
"sendMany with attachments failed:",
317+
receipt.errorMessages,
318+
);
319+
}
320+
receipts.push(receipt);
321+
}
322+
256323
assert.equal(receipts.length, 2);
257324
assert.ok(receipts.every((r) => r.successful));
258325
});

0 commit comments

Comments
 (0)