Send the campaign batch
Node · Node · intermediate · greenfield
Adds the campaign batch sender: a fixed pool of 5 workers walks the recipient list off a shared cursor, so at most 5 sends are ever in flight regardless of list size. Failures are collected per recipient without stopping the batch, and the function resolves with the sent/failed breakdown.
Marketing service for a newsletter product; the provider enforces the 5-concurrent-requests limit with hard 429s.
Requirements
- Implement `sendCampaign(recipients, message)`: send one email per recipient through the existing `sendEmail(email, message)` client (one request per call). `recipients` is a dense array of objects with a string `email`; on failure `sendEmail` rejects with an `Error` whose `message` describes the failure — those are the input and client contracts, values outside them are not valid inputs.
- Campaigns reach up to 50,000 recipients; the email provider allows at most 5 concurrent requests per API key, so no more than 5 sends may be in flight at any moment.
- One failing address must never abort the batch: record it and keep going.
- Return `{ sent, failed }` — `sent` is an array of the delivered addresses, `failed` an array of `{ email, error }` objects with the failure message.
- Completion order across recipients is not significant; an empty recipient list resolves to empty results.
Files touched
- src/campaigns/sendCampaign.js
--- src/campaigns/sendCampaign.js
+const { sendEmail } = require('./emailClient');
+
+const POOL_SIZE = 5;
+
+/**
+ * Send one email per recipient with at most POOL_SIZE requests in flight.
+ * One bad address never aborts the batch.
+ */
+async function sendCampaign(recipients, message) {
+ const sent = [];
+ const failed = [];
+ let next = 0;
+
+ async function worker() {
+ while (next < recipients.length) {
+ const recipient = recipients[next];
+ next += 1;
+ try {
+ await sendEmail(recipient.email, message);
+ sent.push(recipient.email);
+ } catch (error) {
+ failed.push({ email: recipient.email, error: error.message });
+ }
+ }
+ }
+
+ const workerCount = Math.min(POOL_SIZE, recipients.length);
+ const workers = [];
+ for (let i = 0; i < workerCount; i += 1) {