Bulk Sending

Bulk Sending

When sending notifications to many recipients, Facteur provides options to control performance, error handling, and progress tracking.

Basic usage

Pass an array or async iterable of recipients to to(). The current implementation collects an async iterable into an array before sending: it is not a bounded-memory stream. Chunking controls processing, not initial collection.

const users = await User.all()
await facteur.notification(WelcomeNotification).to(users).send()

Chunking and concurrency

For large recipient lists, you can control how notifications are processed.

await facteur
.notification(WelcomeNotification)
.to(users)
.chunkSize(100) // Process 100 recipients at a time
.concurrency(5) // Max 5 concurrent sends per chunk
.send()

Error handling

By default, a recipient failure rejects the bulk send; already-started operations may still complete. Use continueOnError() to process the remaining recipients: send() then resolves with every channel result, including failures and their error, whether you pass zero, one or many recipients, or use via() without to(). Use .throwOnError(false) to return per-channel failures rather than rejecting each recipient. An explicit .throwOnError(true) still stops on the first failing recipient, even with continueOnError().

const result = await facteur.notification(WelcomeNotification).to(users).continueOnError().send()
console.log(`Success: ${result.success}, Failed: ${result.failed}`)

Retries and timeout

Builder retries/timeouts apply to each channel send, exactly like for a single recipient: only the failing channel is retried, and an already successful channel is not sent again. They override the global/per-channel retry configuration. Lifecycle hooks such as beforeSend() and channel/preference resolution are not retried or timed out.

await facteur
.notification(WelcomeNotification)
.to(users)
.retries(3) // Retry up to 3 times on failure
.timeout('30s') // Timeout after 30 seconds
.send()

Progress tracking

Track progress for long-running bulk operations.

await facteur
.notification(WelcomeNotification)
.to(users)
.onProgress((completed, total) => {
console.log(`Progress: ${completed}/${total}`)
})
.send()

Driver batching

Some channels like FCM or Expo support native batch APIs. Enable driver batching to group messages by channel and use these optimized APIs when available.

await facteur.notification(PushNotification).to(users).useDriverBatching().send()

Reference

In driver batch mode:

  • Concurrency controls recipient preparation; channels send their grouped batches independently.
  • Builder retries/timeouts wrap each channel's grouped send operation, not individual recipients. Returned failed batch items are not automatically retried.
  • Progress is reported per completed chunk rather than per recipient.
  • afterSend() and notification-level events are not run; message-level events still fire.
  • Channels without a batch API fall back to individual sends. .disableDriverBatch() forces this fallback while retaining batch-mode orchestration.
  • continueOnError() handles recipient preparation failures; batch item failures are returned as channel results. An explicit .throwOnError(true) stops on the first failing recipient, like in normal mode.

For real delivery, success/failed count channel deliveries, not users. With continueOnError(), a recipient whose channels fail contributes its channel results; a recipient failing before channel sending (for example in beforeSend()) contributes one failure without channel details. One recipient, many recipients and anonymous via() sends all go through the same path, so every option applies the same way.

MethodDescription
chunkSize(n)Number of recipients per chunk (default: unlimited)
concurrency(n)Max concurrent operations per chunk (default: 10)
continueOnError()Continue on individual failures instead of stopping
retries(n)Number of retries with exponential backoff
timeout(duration)Per-operation timeout ('30s', '1m', etc.)
onProgress(callback)Progress callback (completed, total) => void
useDriverBatching()Use channel's native batch API when available
disableDriverBatch()Send individually within driver batch mode
throwOnError(false)Return channel failures in normal mode instead of throwing