@bernierllc/email-batch-sender 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
package/README.md CHANGED
@@ -1,45 +1,83 @@
1
1
  # @bernierllc/email-batch-sender
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Batch email sending with concurrency control, rate limiting, retry strategies, and failure handling. Provides a unified batch API across all email providers, using native provider batch endpoints where available and falling back to sequential sending otherwise.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Installation
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ ```bash
8
+ npm install @bernierllc/email-batch-sender
9
+ ```
8
10
 
9
- ## Purpose
11
+ ## Usage
10
12
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@bernierllc/email-batch-sender`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
13
+ ```typescript
14
+ import { createBatchSender } from '@bernierllc/email-batch-sender';
15
15
 
16
- ## What is OIDC Trusted Publishing?
16
+ const sender = createBatchSender(emailSenderInstance, {
17
+ concurrency: 5,
18
+ rateLimit: { tokensPerInterval: 10, interval: 1000 },
19
+ retry: { maxRetries: 3, backoffMs: 1000 },
20
+ });
17
21
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
22
+ const result = await sender.sendBatch([
23
+ { to: 'alice@example.com', subject: 'Hello', html: '<p>Hi Alice</p>' },
24
+ { to: 'bob@example.com', subject: 'Hello', html: '<p>Hi Bob</p>' },
25
+ ]);
19
26
 
20
- ## Setup Instructions
27
+ console.log(`Sent: ${result.succeeded} / ${result.total}`);
28
+ ```
21
29
 
22
- To properly configure OIDC trusted publishing for this package:
30
+ ## Exports
23
31
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
32
+ - `BatchSender` -- main class for batch operations
33
+ - `createBatchSender()` -- factory function
34
+ - `TokenBucketRateLimiter` -- rate limiter used internally
35
+ - `BatchSenderError` -- custom error class with error codes
36
+ - Types: `BatchSenderOptions`, `RateLimitOptions`, `BatchRetryOptions`, `BatchResult`, `BatchEmailResult`, `BatchProgress`, `BatchEmailInput`
28
37
 
29
- ## DO NOT USE THIS PACKAGE
38
+ ## Provider Capability Support
30
39
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
40
+ This section documents how batch sending behaves across email providers, based on the canonical `CAPABILITY_MATRIX` in `@bernierllc/email-manager`.
36
41
 
37
- ## More Information
42
+ ### Capability Levels by Provider
38
43
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
44
+ | Provider | Capability Level | Notes |
45
+ |----------|-----------------|-------|
46
+ | SendGrid | **provider** | Native batch API via personalizations; the provider handles batching server-side |
47
+ | Mailgun | **provider** | Native batch send via recipient variables |
48
+ | Postmark | **provider** | Native batch API endpoint (`/email/batch`) |
49
+ | SES | **provider** | Native `SendBulkEmail` API |
50
+ | SMTP | **platform** | No native batch support; this package sends emails sequentially over the SMTP connection |
42
51
 
43
- ---
52
+ ### How It Works
44
53
 
45
- **Maintained for OIDC setup purposes only**
54
+ - **Provider-level** (SendGrid, Mailgun, Postmark, SES): The email manager delegates to the provider's native batch endpoint. This package orchestrates the calls, handles rate limiting, and manages retries. The provider may process the batch as a single API call (SendGrid personalizations, Postmark batch endpoint) or as optimized bulk operations (SES `SendBulkEmail`).
55
+
56
+ - **Platform-level** (SMTP): Since SMTP has no native batch support, this package sends emails sequentially over the SMTP connection. Concurrency, rate limiting, and retry logic are all handled at the platform level by this package.
57
+
58
+ ### Degradation Behavior
59
+
60
+ When used with a provider that lacks native batch support (currently only SMTP):
61
+
62
+ - **Strategy**: `sequential-batch`
63
+ - **Behavior**: Emails are sent one at a time over the SMTP connection, respecting the configured concurrency limit and rate limiter
64
+ - **Performance**: Slower than native batch APIs; throughput depends on SMTP server response times and the configured concurrency/rate-limit settings
65
+ - **Reliability**: Full retry support with configurable backoff; individual message failures do not block the rest of the batch
66
+
67
+ ### Override Options
68
+
69
+ The batch send capability is marked `overridable: true` for all providers with native support, meaning you can force platform-level sequential sending even when a native batch API is available. This can be useful for debugging or when provider batch endpoints have temporary issues.
70
+
71
+ ```typescript
72
+ // Force sequential sending regardless of provider
73
+ const sender = createBatchSender(emailSenderInstance, {
74
+ forceSequential: true,
75
+ concurrency: 3,
76
+ });
77
+ ```
78
+
79
+ ## License
80
+
81
+ Copyright (c) 2025 Bernier LLC. All rights reserved.
82
+
83
+ This package is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Batch email sender with concurrency control, rate limiting, and failure strategies
3
+ */
4
+ import { BatchSenderOptions, BatchResult, BatchEmailInput } from './types';
5
+ /**
6
+ * Interface for the underlying email sender.
7
+ * Matches the shape of @bernierllc/email-sender's EmailSender.sendEmail() return type.
8
+ */
9
+ export interface EmailSenderInterface {
10
+ send(message: {
11
+ toEmail: string;
12
+ toName?: string;
13
+ subject: string;
14
+ htmlContent?: string;
15
+ textContent?: string;
16
+ headers?: Record<string, string>;
17
+ attachments?: readonly {
18
+ filename: string;
19
+ content: string;
20
+ contentType?: string;
21
+ }[];
22
+ metadata?: Record<string, unknown>;
23
+ }): Promise<{
24
+ success: boolean;
25
+ messageId?: string;
26
+ errorMessage?: string;
27
+ provider?: string;
28
+ }>;
29
+ }
30
+ export declare class BatchSender {
31
+ private readonly sender;
32
+ private readonly options;
33
+ private aborted;
34
+ private readonly concurrency;
35
+ private readonly maxRetries;
36
+ private readonly backoffMs;
37
+ private readonly backoffMultiplier;
38
+ private readonly rateLimiter;
39
+ constructor(sender: EmailSenderInterface, options?: BatchSenderOptions);
40
+ /**
41
+ * Send a batch of emails with concurrency control, rate limiting, and retry.
42
+ */
43
+ send(emails: readonly BatchEmailInput[]): Promise<BatchResult>;
44
+ /**
45
+ * Abort the current batch send. In-flight emails will finish, remaining will be skipped.
46
+ */
47
+ abort(): void;
48
+ private sendWithRetry;
49
+ private buildSuccessResult;
50
+ private shouldAbortOnThreshold;
51
+ private emitProgress;
52
+ private validateEmails;
53
+ private sleep;
54
+ }
55
+ /**
56
+ * Factory function to create a BatchSender instance.
57
+ */
58
+ export declare function createBatchSender(sender: EmailSenderInterface, options?: BatchSenderOptions): BatchSender;
59
+ //# sourceMappingURL=batch-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-sender.d.ts","sourceRoot":"","sources":["../src/batch-sender.ts"],"names":[],"mappings":"AAQA;;GAEG;AAEH,OAAO,EACL,kBAAkB,EAClB,WAAW,EAGX,eAAe,EAChB,MAAM,SAAS,CAAC;AAIjB;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,SAAS;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACrF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;AAOD,qBAAa,WAAW;IASpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAT1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;gBAG9C,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE,kBAAuB;IAgDnD;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAoHpE;;OAEG;IACH,KAAK,IAAI,IAAI;YAIC,aAAa;IA2E3B,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,cAAc;IA8BtB,OAAO,CAAC,KAAK;CAGd;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,WAAW,CAEb"}
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BatchSender = void 0;
11
+ exports.createBatchSender = createBatchSender;
12
+ const errors_1 = require("./errors");
13
+ const rate_limiter_1 = require("./rate-limiter");
14
+ const DEFAULT_CONCURRENCY = 5;
15
+ const DEFAULT_MAX_RETRIES = 3;
16
+ const DEFAULT_BACKOFF_MS = 1000;
17
+ const DEFAULT_BACKOFF_MULTIPLIER = 2;
18
+ class BatchSender {
19
+ constructor(sender, options = {}) {
20
+ this.sender = sender;
21
+ this.options = options;
22
+ this.aborted = false;
23
+ this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
24
+ this.maxRetries = options.retryOptions?.maxRetries ?? DEFAULT_MAX_RETRIES;
25
+ this.backoffMs = options.retryOptions?.backoffMs ?? DEFAULT_BACKOFF_MS;
26
+ this.backoffMultiplier =
27
+ options.retryOptions?.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER;
28
+ if (options.rateLimit) {
29
+ this.rateLimiter = new rate_limiter_1.TokenBucketRateLimiter(options.rateLimit);
30
+ }
31
+ if (this.concurrency < 1) {
32
+ throw new errors_1.BatchSenderError('Concurrency must be at least 1', {
33
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
34
+ context: { concurrency: this.concurrency },
35
+ });
36
+ }
37
+ if (options.failureStrategy === 'abort-on-threshold' &&
38
+ (options.failureThreshold === undefined ||
39
+ options.failureThreshold === null)) {
40
+ throw new errors_1.BatchSenderError('failureThreshold is required when using abort-on-threshold strategy', {
41
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
42
+ context: { failureStrategy: options.failureStrategy },
43
+ });
44
+ }
45
+ if (options.failureThreshold !== undefined &&
46
+ options.failureThreshold !== null &&
47
+ (options.failureThreshold < 0 || options.failureThreshold > 100)) {
48
+ throw new errors_1.BatchSenderError('failureThreshold must be between 0 and 100', {
49
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
50
+ context: { failureThreshold: options.failureThreshold },
51
+ });
52
+ }
53
+ }
54
+ /**
55
+ * Send a batch of emails with concurrency control, rate limiting, and retry.
56
+ */
57
+ async send(emails) {
58
+ if (emails.length === 0) {
59
+ return {
60
+ total: 0,
61
+ succeeded: 0,
62
+ failed: 0,
63
+ results: [],
64
+ duration: 0,
65
+ aborted: false,
66
+ };
67
+ }
68
+ this.validateEmails(emails);
69
+ this.aborted = false;
70
+ const startTime = Date.now();
71
+ const results = [];
72
+ let succeeded = 0;
73
+ let failed = 0;
74
+ let aborted = false;
75
+ // Promise pool pattern for concurrency control
76
+ const queue = [...emails];
77
+ let queueIndex = 0;
78
+ const activePromises = [];
79
+ const processNext = async () => {
80
+ while (queueIndex < queue.length) {
81
+ if (this.aborted) {
82
+ break;
83
+ }
84
+ // Check threshold abort
85
+ if (this.shouldAbortOnThreshold(failed, results.length, emails.length)) {
86
+ this.aborted = true;
87
+ break;
88
+ }
89
+ const currentIndex = queueIndex;
90
+ queueIndex++;
91
+ const email = queue[currentIndex];
92
+ if (!email) {
93
+ break;
94
+ }
95
+ // Rate limiting
96
+ if (this.rateLimiter) {
97
+ await this.rateLimiter.acquire();
98
+ }
99
+ if (this.aborted) {
100
+ break;
101
+ }
102
+ const result = await this.sendWithRetry(email);
103
+ results.push(result);
104
+ if (result.success) {
105
+ succeeded++;
106
+ }
107
+ else {
108
+ failed++;
109
+ // Check abort-on-first
110
+ if (this.options.failureStrategy === 'abort-on-first') {
111
+ this.aborted = true;
112
+ }
113
+ // Check abort-on-threshold after each failure
114
+ if (this.shouldAbortOnThreshold(failed, results.length, emails.length)) {
115
+ this.aborted = true;
116
+ }
117
+ }
118
+ this.emitProgress(emails.length, results.length, succeeded, failed, startTime);
119
+ }
120
+ };
121
+ // Launch workers up to concurrency limit
122
+ const workerCount = Math.min(this.concurrency, emails.length);
123
+ for (let i = 0; i < workerCount; i++) {
124
+ activePromises.push(processNext());
125
+ }
126
+ await Promise.all(activePromises);
127
+ aborted = this.aborted;
128
+ // Mark remaining emails as skipped if aborted
129
+ if (aborted) {
130
+ for (let i = results.length; i < emails.length; i++) {
131
+ const email = emails[i];
132
+ if (email) {
133
+ results.push({
134
+ recipient: email.toEmail,
135
+ success: false,
136
+ error: 'Batch aborted',
137
+ attempts: 0,
138
+ });
139
+ failed++;
140
+ }
141
+ }
142
+ }
143
+ const duration = Date.now() - startTime;
144
+ return {
145
+ total: emails.length,
146
+ succeeded,
147
+ failed,
148
+ results,
149
+ duration,
150
+ aborted,
151
+ };
152
+ }
153
+ /**
154
+ * Abort the current batch send. In-flight emails will finish, remaining will be skipped.
155
+ */
156
+ abort() {
157
+ this.aborted = true;
158
+ }
159
+ async sendWithRetry(email) {
160
+ let lastError;
161
+ let attempts = 0;
162
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
163
+ attempts = attempt + 1;
164
+ if (this.aborted && attempt > 0) {
165
+ return {
166
+ recipient: email.toEmail,
167
+ success: false,
168
+ error: 'Batch aborted during retry',
169
+ attempts,
170
+ };
171
+ }
172
+ try {
173
+ const sendMessage = {
174
+ toEmail: email.toEmail,
175
+ subject: email.subject,
176
+ };
177
+ if (email.toName !== undefined) {
178
+ sendMessage.toName = email.toName;
179
+ }
180
+ if (email.htmlContent !== undefined) {
181
+ sendMessage.htmlContent = email.htmlContent;
182
+ }
183
+ if (email.textContent !== undefined) {
184
+ sendMessage.textContent = email.textContent;
185
+ }
186
+ if (email.headers !== undefined) {
187
+ sendMessage.headers = email.headers;
188
+ }
189
+ if (email.attachments !== undefined) {
190
+ sendMessage.attachments = email.attachments;
191
+ }
192
+ if (email.metadata !== undefined) {
193
+ sendMessage.metadata = email.metadata;
194
+ }
195
+ const result = await this.sender.send(sendMessage);
196
+ if (result.success) {
197
+ return this.buildSuccessResult(email.toEmail, attempts, result.messageId, result.provider);
198
+ }
199
+ lastError = result.errorMessage ?? 'Send failed without error message';
200
+ }
201
+ catch (error) {
202
+ lastError =
203
+ error instanceof Error ? error.message : 'Unknown send error';
204
+ }
205
+ // Wait before retry (skip wait on last attempt)
206
+ if (attempt < this.maxRetries) {
207
+ const delay = this.backoffMs * Math.pow(this.backoffMultiplier, attempt);
208
+ await this.sleep(delay);
209
+ }
210
+ }
211
+ const failResult = {
212
+ recipient: email.toEmail,
213
+ success: false,
214
+ attempts,
215
+ };
216
+ if (lastError !== undefined) {
217
+ return { ...failResult, error: lastError };
218
+ }
219
+ return failResult;
220
+ }
221
+ buildSuccessResult(recipient, attempts, messageId, provider) {
222
+ const base = { recipient, success: true, attempts };
223
+ if (messageId !== undefined && provider !== undefined) {
224
+ return { ...base, messageId, provider };
225
+ }
226
+ if (messageId !== undefined) {
227
+ return { ...base, messageId };
228
+ }
229
+ if (provider !== undefined) {
230
+ return { ...base, provider };
231
+ }
232
+ return base;
233
+ }
234
+ shouldAbortOnThreshold(failedCount, completedCount, _totalCount) {
235
+ if (this.options.failureStrategy !== 'abort-on-threshold') {
236
+ return false;
237
+ }
238
+ if (completedCount === 0) {
239
+ return false;
240
+ }
241
+ const failurePercentage = (failedCount / completedCount) * 100;
242
+ return failurePercentage >= (this.options.failureThreshold ?? 100);
243
+ }
244
+ emitProgress(total, completed, succeededCount, failedCount, startTime) {
245
+ if (!this.options.onProgress) {
246
+ return;
247
+ }
248
+ const elapsed = (Date.now() - startTime) / 1000;
249
+ const currentRate = elapsed > 0 ? completed / elapsed : 0;
250
+ const progress = {
251
+ total,
252
+ completed,
253
+ succeeded: succeededCount,
254
+ failed: failedCount,
255
+ currentRate,
256
+ };
257
+ this.options.onProgress(progress);
258
+ }
259
+ validateEmails(emails) {
260
+ for (let i = 0; i < emails.length; i++) {
261
+ const email = emails[i];
262
+ if (!email) {
263
+ throw new errors_1.BatchSenderError(`Email at index ${i} is undefined`, {
264
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
265
+ context: { index: i },
266
+ });
267
+ }
268
+ if (!email.toEmail) {
269
+ throw new errors_1.BatchSenderError(`Email at index ${i} is missing required field: toEmail`, {
270
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
271
+ context: { index: i },
272
+ });
273
+ }
274
+ if (!email.subject) {
275
+ throw new errors_1.BatchSenderError(`Email at index ${i} is missing required field: subject`, {
276
+ code: errors_1.BatchSenderErrorCode.INVALID_INPUT,
277
+ context: { index: i },
278
+ });
279
+ }
280
+ }
281
+ }
282
+ sleep(ms) {
283
+ return new Promise((resolve) => setTimeout(resolve, ms));
284
+ }
285
+ }
286
+ exports.BatchSender = BatchSender;
287
+ /**
288
+ * Factory function to create a BatchSender instance.
289
+ */
290
+ function createBatchSender(sender, options) {
291
+ return new BatchSender(sender, options);
292
+ }
293
+ //# sourceMappingURL=batch-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-sender.js","sourceRoot":"","sources":["../src/batch-sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Batch sender error classes with ES2022 Error.cause support
3
+ */
4
+ export declare const BatchSenderErrorCode: {
5
+ readonly INVALID_INPUT: "INVALID_INPUT";
6
+ readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
7
+ readonly BATCH_ABORTED: "BATCH_ABORTED";
8
+ readonly SEND_FAILED: "SEND_FAILED";
9
+ };
10
+ export type BatchSenderErrorCodeType = (typeof BatchSenderErrorCode)[keyof typeof BatchSenderErrorCode];
11
+ export interface BatchSenderErrorOptions {
12
+ cause?: Error;
13
+ code?: BatchSenderErrorCodeType;
14
+ context?: Record<string, unknown>;
15
+ }
16
+ export declare class BatchSenderError extends Error {
17
+ readonly code: BatchSenderErrorCodeType;
18
+ readonly context: Record<string, unknown> | undefined;
19
+ constructor(message: string, options?: BatchSenderErrorOptions);
20
+ }
21
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAQA;;GAEG;AAEH,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAEnE,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;gBAE1C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB;CAY/D"}
package/dist/errors.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BatchSenderError = exports.BatchSenderErrorCode = void 0;
11
+ /**
12
+ * Batch sender error classes with ES2022 Error.cause support
13
+ */
14
+ exports.BatchSenderErrorCode = {
15
+ INVALID_INPUT: 'INVALID_INPUT',
16
+ RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
17
+ BATCH_ABORTED: 'BATCH_ABORTED',
18
+ SEND_FAILED: 'SEND_FAILED',
19
+ };
20
+ class BatchSenderError extends Error {
21
+ constructor(message, options) {
22
+ super(message);
23
+ this.name = 'BatchSenderError';
24
+ this.code = options?.code ?? exports.BatchSenderErrorCode.SEND_FAILED;
25
+ this.context = options?.context;
26
+ if (options?.cause) {
27
+ this.cause = options.cause;
28
+ }
29
+ Error.captureStackTrace?.(this, this.constructor);
30
+ }
31
+ }
32
+ exports.BatchSenderError = BatchSenderError;
33
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;;;;;EAME;;;AAEF;;GAEG;AAEU,QAAA,oBAAoB,GAAG;IAClC,aAAa,EAAE,eAAe;IAC9B,mBAAmB,EAAE,qBAAqB;IAC1C,aAAa,EAAE,eAAe;IAC9B,WAAW,EAAE,aAAa;CAClB,CAAC;AAWX,MAAa,gBAAiB,SAAQ,KAAK;IAIzC,YAAY,OAAe,EAAE,OAAiC;QAC5D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,4BAAoB,CAAC,WAAW,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC;QAEhC,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YAClB,IAAoC,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC9D,CAAC;QAED,KAAK,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC;CACF;AAhBD,4CAgBC"}
@@ -0,0 +1,5 @@
1
+ export { BatchSenderOptions, RateLimitOptions, BatchRetryOptions, BatchResult, BatchEmailResult, BatchProgress, BatchEmailInput, } from './types';
2
+ export { BatchSenderError, BatchSenderErrorCode, BatchSenderErrorCodeType, BatchSenderErrorOptions, } from './errors';
3
+ export { TokenBucketRateLimiter } from './rate-limiter';
4
+ export { BatchSender, EmailSenderInterface, createBatchSender, } from './batch-sender';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createBatchSender = exports.BatchSender = exports.TokenBucketRateLimiter = exports.BatchSenderErrorCode = exports.BatchSenderError = void 0;
11
+ var errors_1 = require("./errors");
12
+ Object.defineProperty(exports, "BatchSenderError", { enumerable: true, get: function () { return errors_1.BatchSenderError; } });
13
+ Object.defineProperty(exports, "BatchSenderErrorCode", { enumerable: true, get: function () { return errors_1.BatchSenderErrorCode; } });
14
+ var rate_limiter_1 = require("./rate-limiter");
15
+ Object.defineProperty(exports, "TokenBucketRateLimiter", { enumerable: true, get: function () { return rate_limiter_1.TokenBucketRateLimiter; } });
16
+ var batch_sender_1 = require("./batch-sender");
17
+ Object.defineProperty(exports, "BatchSender", { enumerable: true, get: function () { return batch_sender_1.BatchSender; } });
18
+ Object.defineProperty(exports, "createBatchSender", { enumerable: true, get: function () { return batch_sender_1.createBatchSender; } });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;EAME;;;AAYF,mCAKkB;AAJhB,0GAAA,gBAAgB,OAAA;AAChB,8GAAA,oBAAoB,OAAA;AAKtB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA;AAE/B,+CAIwB;AAHtB,2GAAA,WAAW,OAAA;AAEX,iHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Token bucket rate limiter for controlling email send rates
3
+ */
4
+ import { RateLimitOptions } from './types';
5
+ export declare class TokenBucketRateLimiter {
6
+ private readonly maxPerSecond;
7
+ private readonly maxPerMinute;
8
+ private secondTokens;
9
+ private minuteTokens;
10
+ private lastSecondRefill;
11
+ private lastMinuteRefill;
12
+ constructor(options: RateLimitOptions);
13
+ /**
14
+ * Acquire a token, waiting if necessary until one is available.
15
+ */
16
+ acquire(): Promise<void>;
17
+ private refill;
18
+ private calculateWaitTime;
19
+ private sleep;
20
+ }
21
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAQA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,OAAO,EAAE,gBAAgB;IASrC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB9B,OAAO,CAAC,MAAM;IA0Bd,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.TokenBucketRateLimiter = void 0;
11
+ class TokenBucketRateLimiter {
12
+ constructor(options) {
13
+ this.maxPerSecond = options.maxPerSecond ?? Infinity;
14
+ this.maxPerMinute = options.maxPerMinute ?? Infinity;
15
+ this.secondTokens = this.maxPerSecond;
16
+ this.minuteTokens = this.maxPerMinute;
17
+ this.lastSecondRefill = Date.now();
18
+ this.lastMinuteRefill = Date.now();
19
+ }
20
+ /**
21
+ * Acquire a token, waiting if necessary until one is available.
22
+ */
23
+ async acquire() {
24
+ // eslint-disable-next-line no-constant-condition
25
+ while (true) {
26
+ this.refill();
27
+ if (this.secondTokens >= 1 && this.minuteTokens >= 1) {
28
+ this.secondTokens -= 1;
29
+ this.minuteTokens -= 1;
30
+ return;
31
+ }
32
+ const waitMs = this.calculateWaitTime();
33
+ await this.sleep(waitMs);
34
+ }
35
+ }
36
+ refill() {
37
+ const now = Date.now();
38
+ // Refill second bucket
39
+ const secondsElapsed = (now - this.lastSecondRefill) / 1000;
40
+ if (secondsElapsed >= 1) {
41
+ const tokensToAdd = Math.floor(secondsElapsed) * this.maxPerSecond;
42
+ this.secondTokens = Math.min(this.maxPerSecond, this.secondTokens + tokensToAdd);
43
+ this.lastSecondRefill = now - ((secondsElapsed % 1) * 1000);
44
+ }
45
+ // Refill minute bucket
46
+ const minutesElapsed = (now - this.lastMinuteRefill) / 60000;
47
+ if (minutesElapsed >= 1) {
48
+ const tokensToAdd = Math.floor(minutesElapsed) * this.maxPerMinute;
49
+ this.minuteTokens = Math.min(this.maxPerMinute, this.minuteTokens + tokensToAdd);
50
+ this.lastMinuteRefill = now - ((minutesElapsed % 1) * 60000);
51
+ }
52
+ }
53
+ calculateWaitTime() {
54
+ if (this.secondTokens < 1 && this.maxPerSecond !== Infinity) {
55
+ const elapsed = (Date.now() - this.lastSecondRefill) / 1000;
56
+ const remaining = Math.max(0, 1 - elapsed);
57
+ return Math.ceil(remaining * 1000);
58
+ }
59
+ if (this.minuteTokens < 1 && this.maxPerMinute !== Infinity) {
60
+ const elapsed = (Date.now() - this.lastMinuteRefill) / 60000;
61
+ const remaining = Math.max(0, 1 - elapsed);
62
+ return Math.ceil(remaining * 60000);
63
+ }
64
+ return 10;
65
+ }
66
+ sleep(ms) {
67
+ return new Promise((resolve) => setTimeout(resolve, ms));
68
+ }
69
+ }
70
+ exports.TokenBucketRateLimiter = TokenBucketRateLimiter;
71
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Batch email sender type definitions
3
+ */
4
+ export interface BatchSenderOptions {
5
+ readonly concurrency?: number;
6
+ readonly rateLimit?: RateLimitOptions;
7
+ readonly retryOptions?: BatchRetryOptions;
8
+ readonly onProgress?: (progress: BatchProgress) => void;
9
+ readonly failureStrategy?: 'continue' | 'abort-on-first' | 'abort-on-threshold';
10
+ readonly failureThreshold?: number;
11
+ }
12
+ export interface RateLimitOptions {
13
+ readonly maxPerSecond?: number;
14
+ readonly maxPerMinute?: number;
15
+ }
16
+ export interface BatchRetryOptions {
17
+ readonly maxRetries?: number;
18
+ readonly backoffMs?: number;
19
+ readonly backoffMultiplier?: number;
20
+ }
21
+ export interface BatchResult {
22
+ readonly total: number;
23
+ readonly succeeded: number;
24
+ readonly failed: number;
25
+ readonly results: readonly BatchEmailResult[];
26
+ readonly duration: number;
27
+ readonly aborted: boolean;
28
+ }
29
+ export interface BatchEmailResult {
30
+ readonly recipient: string;
31
+ readonly success: boolean;
32
+ readonly messageId?: string;
33
+ readonly provider?: string;
34
+ readonly error?: string;
35
+ readonly attempts: number;
36
+ }
37
+ export interface BatchProgress {
38
+ readonly total: number;
39
+ readonly completed: number;
40
+ readonly succeeded: number;
41
+ readonly failed: number;
42
+ readonly currentRate: number;
43
+ }
44
+ export interface BatchEmailInput {
45
+ readonly toEmail: string;
46
+ readonly toName?: string;
47
+ readonly subject: string;
48
+ readonly htmlContent?: string;
49
+ readonly textContent?: string;
50
+ readonly headers?: Record<string, string>;
51
+ readonly attachments?: readonly {
52
+ filename: string;
53
+ content: string;
54
+ contentType?: string;
55
+ }[];
56
+ readonly metadata?: Record<string, unknown>;
57
+ }
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,UAAU,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;IAChF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,EAAE,CAAC;IACJ,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C"}
package/dist/types.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;EAME"}
package/package.json CHANGED
@@ -1,10 +1,55 @@
1
1
  {
2
2
  "name": "@bernierllc/email-batch-sender",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @bernierllc/email-batch-sender",
3
+ "version": "0.1.0",
4
+ "description": "Batch email sending with concurrency control, rate limiting, retry strategies, and failure handling",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
5
12
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
13
+ "email",
14
+ "batch",
15
+ "sender",
16
+ "concurrency",
17
+ "rate-limiting",
18
+ "retry",
19
+ "typescript"
20
+ ],
21
+ "author": "Bernier LLC",
22
+ "license": "PROPRIETARY",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/bernier-llc/tools"
26
+ },
27
+ "dependencies": {
28
+ "@bernierllc/email-sender": "5.0.0",
29
+ "@bernierllc/retry-policy": "0.3.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/jest": "^29.5.0",
33
+ "@types/node": "^20.0.0",
34
+ "jest": "^29.5.0",
35
+ "ts-jest": "^29.1.0",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "registry": "https://registry.npmjs.org/"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "test": "jest",
48
+ "test:run": "jest --watchAll=false --forceExit",
49
+ "test:coverage": "jest --coverage",
50
+ "lint": "eslint src --ext .ts",
51
+ "lint:fix": "eslint src --ext .ts --fix",
52
+ "clean": "rm -rf dist",
53
+ "prebuild": "npm run clean"
54
+ }
55
+ }