@bernierllc/email-batch-sender 0.0.1 → 0.2.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 +7 -0
- package/README.md +66 -28
- package/dist/batch-sender.d.ts +59 -0
- package/dist/batch-sender.d.ts.map +1 -0
- package/dist/batch-sender.js +293 -0
- package/dist/batch-sender.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/rate-limiter.d.ts +21 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +71 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -7
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
|
-
|
|
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
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bernierllc/email-batch-sender
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Usage
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
+
const sender = createBatchSender(emailSenderInstance, {
|
|
17
|
+
concurrency: 5,
|
|
18
|
+
rateLimit: { tokensPerInterval: 10, interval: 1000 },
|
|
19
|
+
retry: { maxRetries: 3, backoffMs: 1000 },
|
|
20
|
+
});
|
|
17
21
|
|
|
18
|
-
|
|
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
|
-
|
|
27
|
+
console.log(`Sent: ${result.succeeded} / ${result.total}`);
|
|
28
|
+
```
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
## Exports
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
##
|
|
38
|
+
## Provider Capability Support
|
|
30
39
|
|
|
31
|
-
This
|
|
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
|
-
|
|
42
|
+
### Capability Levels by Provider
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
**
|
|
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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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.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
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
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.1.1",
|
|
29
|
+
"@bernierllc/retry-policy": "0.3.1"
|
|
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
|
+
}
|