What happens
driverHandler() checks ctx.signal?.aborted only in the sequential branch. When the driver has a native sendBatch and there is more than one message, the batch is handed to the provider with no abort check at all — an already-aborted signal sends the mail anyway.
Reproduction
const controller = new AbortController()
controller.abort() // aborted before anything starts
const driver = mock() // mock has sendBatch
const email = createEmail({ driver, defaults: { from: "f@x.com" }, signal: controller.signal })
const batch = await email.sendBatch([msg, msg])
Observed:
results: ok,ok
driver saw: 2 messages
Expected: two CANCELLED results and nothing delivered. A single send() on the same instance is cancelled correctly, because one message takes the sequential path — so the behaviour differs by batch size.
Why it matters
signal is the documented way to cancel in-flight sends when a request is torn down or a job is killed. Silently sending anyway is the one failure mode a cancel API must not have, and it is worse in a batch, where the volume is higher.
Where
src/core/define.ts:115-135 — the msgs.length > 1 && driver.sendBatch branch returns before reaching the ctx.signal?.aborted check at line 139.
Suggested fix
Check the signal once at the top of the handler, before choosing a branch, and return CANCELLED for every message. Keep the per-message check in the sequential loop as well, so an abort arriving mid-loop still stops the remainder.
What happens
driverHandler()checksctx.signal?.abortedonly in the sequential branch. When the driver has a nativesendBatchand there is more than one message, the batch is handed to the provider with no abort check at all — an already-aborted signal sends the mail anyway.Reproduction
Observed:
Expected: two
CANCELLEDresults and nothing delivered. A singlesend()on the same instance is cancelled correctly, because one message takes the sequential path — so the behaviour differs by batch size.Why it matters
signalis the documented way to cancel in-flight sends when a request is torn down or a job is killed. Silently sending anyway is the one failure mode a cancel API must not have, and it is worse in a batch, where the volume is higher.Where
src/core/define.ts:115-135— themsgs.length > 1 && driver.sendBatchbranch returns before reaching thectx.signal?.abortedcheck at line 139.Suggested fix
Check the signal once at the top of the handler, before choosing a branch, and return
CANCELLEDfor every message. Keep the per-message check in the sequential loop as well, so an abort arriving mid-loop still stops the remainder.