@aws-blocks/bb-email-client 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 +174 -0
- package/README.md +145 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/index.aws.d.ts +59 -0
- package/dist/index.aws.d.ts.map +1 -0
- package/dist/index.aws.js +194 -0
- package/dist/index.browser.d.ts +4 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +6 -0
- package/dist/index.cdk.d.ts +9 -0
- package/dist/index.cdk.d.ts.map +1 -0
- package/dist/index.cdk.js +36 -0
- package/dist/index.mock.d.ts +58 -0
- package/dist/index.mock.d.ts.map +1 -0
- package/dist/index.mock.js +179 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +274 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +44 -0
- package/src/errors.ts +28 -0
- package/src/index.aws.ts +220 -0
- package/src/index.browser.ts +7 -0
- package/src/index.cdk.ts +49 -0
- package/src/index.mock.ts +231 -0
- package/src/index.test.ts +326 -0
- package/src/types.ts +81 -0
- package/src/version.ts +3 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { test, beforeEach } from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
import { rmSync, existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { EmailClient, EmailErrors } from './index.mock.js';
|
|
9
|
+
|
|
10
|
+
// Clean mock data between tests to avoid cross-contamination
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
try { rmSync('.bb-data', { recursive: true, force: true }); } catch {}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// ── Single recipient ────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
test('send to single recipient returns messageId', async () => {
|
|
18
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'test', {
|
|
19
|
+
fromAddress: 'noreply@example.com',
|
|
20
|
+
});
|
|
21
|
+
const result = await emailClient.send({
|
|
22
|
+
to: 'user@example.com',
|
|
23
|
+
subject: 'Hello',
|
|
24
|
+
body: 'World',
|
|
25
|
+
});
|
|
26
|
+
assert.ok(result.messageId);
|
|
27
|
+
assert.ok(result.messageId.startsWith('mock-'));
|
|
28
|
+
// Verify persistence
|
|
29
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-test');
|
|
30
|
+
assert.ok(existsSync(join(dataDir, 'emails.json')));
|
|
31
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
32
|
+
assert.strictEqual(stored.length, 1);
|
|
33
|
+
assert.strictEqual(stored[0].to, 'user@example.com');
|
|
34
|
+
assert.strictEqual(stored[0].subject, 'Hello');
|
|
35
|
+
assert.strictEqual(stored[0].body, 'World');
|
|
36
|
+
assert.strictEqual(stored[0].messageId, result.messageId);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ── Multiple recipients ─────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
test('send to multiple recipients', async () => {
|
|
42
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'multi', {
|
|
43
|
+
fromAddress: 'noreply@example.com',
|
|
44
|
+
});
|
|
45
|
+
const result = await emailClient.send({
|
|
46
|
+
to: ['alice@example.com', 'bob@example.com'],
|
|
47
|
+
subject: 'Team Update',
|
|
48
|
+
body: 'Check this out',
|
|
49
|
+
});
|
|
50
|
+
assert.ok(result.messageId);
|
|
51
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-multi');
|
|
52
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
53
|
+
assert.strictEqual(stored.length, 1);
|
|
54
|
+
assert.deepStrictEqual(stored[0].to, ['alice@example.com', 'bob@example.com']);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ── HTML email support ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
test('send HTML email', async () => {
|
|
60
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'html', {
|
|
61
|
+
fromAddress: 'noreply@example.com',
|
|
62
|
+
});
|
|
63
|
+
const result = await emailClient.send({
|
|
64
|
+
to: 'user@example.com',
|
|
65
|
+
subject: 'Rich Email',
|
|
66
|
+
body: 'Plain fallback',
|
|
67
|
+
html: '<h1>Hello</h1>',
|
|
68
|
+
});
|
|
69
|
+
assert.ok(result.messageId);
|
|
70
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-html');
|
|
71
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
72
|
+
assert.strictEqual(stored[0].html, '<h1>Hello</h1>');
|
|
73
|
+
assert.strictEqual(stored[0].body, 'Plain fallback');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ── Invalid address rejection ───────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
test('rejects invalid recipient address', async () => {
|
|
79
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'invalid', {
|
|
80
|
+
fromAddress: 'noreply@example.com',
|
|
81
|
+
});
|
|
82
|
+
await assert.rejects(
|
|
83
|
+
() => emailClient.send({ to: 'not-an-email', subject: 'Test', body: 'Body' }),
|
|
84
|
+
(err: Error) => err.name === EmailErrors.InvalidInput,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('rejects invalid from address', async () => {
|
|
89
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'badfrom', {
|
|
90
|
+
fromAddress: 'bad-address',
|
|
91
|
+
});
|
|
92
|
+
await assert.rejects(
|
|
93
|
+
() => emailClient.send({ to: 'user@example.com', subject: 'Test', body: 'Body' }),
|
|
94
|
+
(err: Error) => err.name === EmailErrors.InvalidInput,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('rejects if any address in array is invalid', async () => {
|
|
99
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'mixedaddr', {
|
|
100
|
+
fromAddress: 'noreply@example.com',
|
|
101
|
+
});
|
|
102
|
+
await assert.rejects(
|
|
103
|
+
() => emailClient.send({ to: ['valid@example.com', 'invalid@@'], subject: 'Test', body: 'Body' }),
|
|
104
|
+
(err: Error) => err.name === EmailErrors.InvalidInput,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── Per-message recipient limit (50 recipients) ─────────────────────────────
|
|
109
|
+
|
|
110
|
+
test('rejects single message with more than 50 recipients', async () => {
|
|
111
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'toomany', {
|
|
112
|
+
fromAddress: 'noreply@example.com',
|
|
113
|
+
});
|
|
114
|
+
const recipients = Array.from({ length: 51 }, (_, i) => `user${i}@example.com`);
|
|
115
|
+
await assert.rejects(
|
|
116
|
+
() => emailClient.send({ to: recipients, subject: 'Test', body: 'Body' }),
|
|
117
|
+
(err: Error) => {
|
|
118
|
+
assert.strictEqual(err.name, EmailErrors.InvalidInput);
|
|
119
|
+
assert.ok(err.message.includes('50'));
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('rejects message with combined To + CC + BCC exceeding 50', async () => {
|
|
126
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'combined', {
|
|
127
|
+
fromAddress: 'noreply@example.com',
|
|
128
|
+
});
|
|
129
|
+
const toAddrs = Array.from({ length: 20 }, (_, i) => `to${i}@example.com`);
|
|
130
|
+
const ccAddrs = Array.from({ length: 20 }, (_, i) => `cc${i}@example.com`);
|
|
131
|
+
const bccAddrs = Array.from({ length: 11 }, (_, i) => `bcc${i}@example.com`);
|
|
132
|
+
await assert.rejects(
|
|
133
|
+
() => emailClient.send({ to: toAddrs, subject: 'Test', body: 'Body', cc: ccAddrs, bcc: bccAddrs }),
|
|
134
|
+
(err: Error) => {
|
|
135
|
+
assert.strictEqual(err.name, EmailErrors.InvalidInput);
|
|
136
|
+
assert.ok(err.message.includes('50'));
|
|
137
|
+
return true;
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('allows message with exactly 50 recipients', async () => {
|
|
143
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'exact50', {
|
|
144
|
+
fromAddress: 'noreply@example.com',
|
|
145
|
+
});
|
|
146
|
+
const recipients = Array.from({ length: 50 }, (_, i) => `user${i}@example.com`);
|
|
147
|
+
const result = await emailClient.send({ to: recipients, subject: 'Test', body: 'Body' });
|
|
148
|
+
assert.ok(result.messageId);
|
|
149
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-exact50');
|
|
150
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
151
|
+
assert.strictEqual(stored.length, 1);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── Batch: per-message recipient limit ──────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
test('sendBatch marks message with more than 50 recipients as failed', async () => {
|
|
157
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'batchrecip', {
|
|
158
|
+
fromAddress: 'noreply@example.com',
|
|
159
|
+
});
|
|
160
|
+
const recipients = Array.from({ length: 51 }, (_, i) => `user${i}@example.com`);
|
|
161
|
+
const result = await emailClient.sendBatch([
|
|
162
|
+
{ to: recipients, subject: 'Too many', body: 'Body' },
|
|
163
|
+
]);
|
|
164
|
+
assert.strictEqual(result.results.length, 1);
|
|
165
|
+
assert.strictEqual(result.results[0].status, 'failed');
|
|
166
|
+
assert.ok(result.results[0].error!.includes('50'));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── Batch: returns SendBatchResult ──────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
test('sendBatch returns SendBatchResult with results array in input order', async () => {
|
|
172
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'batchresult', {
|
|
173
|
+
fromAddress: 'noreply@example.com',
|
|
174
|
+
});
|
|
175
|
+
const messages = Array.from({ length: 3 }, (_, i) => ({
|
|
176
|
+
to: `user${i}@example.com`,
|
|
177
|
+
subject: `Msg ${i}`,
|
|
178
|
+
body: `Body ${i}`,
|
|
179
|
+
}));
|
|
180
|
+
const result = await emailClient.sendBatch(messages);
|
|
181
|
+
assert.strictEqual(result.results.length, 3);
|
|
182
|
+
for (let i = 0; i < 3; i++) {
|
|
183
|
+
assert.strictEqual(result.results[i].status, 'success');
|
|
184
|
+
assert.ok(result.results[i].messageId);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('sendBatch reports partial failures in correct positions', async () => {
|
|
189
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'batchpartial', {
|
|
190
|
+
fromAddress: 'noreply@example.com',
|
|
191
|
+
});
|
|
192
|
+
const messages = [
|
|
193
|
+
{ to: 'valid@example.com', subject: 'Good', body: 'Body' },
|
|
194
|
+
{ to: 'invalid@@', subject: 'Bad', body: 'Body' },
|
|
195
|
+
{ to: 'also-valid@example.com', subject: 'Good', body: 'Body' },
|
|
196
|
+
];
|
|
197
|
+
const result = await emailClient.sendBatch(messages);
|
|
198
|
+
assert.strictEqual(result.results.length, 3);
|
|
199
|
+
assert.strictEqual(result.results[0].status, 'success');
|
|
200
|
+
assert.ok(result.results[0].messageId);
|
|
201
|
+
assert.strictEqual(result.results[1].status, 'failed');
|
|
202
|
+
assert.ok(result.results[1].error!.includes('Invalid'));
|
|
203
|
+
assert.strictEqual(result.results[2].status, 'success');
|
|
204
|
+
assert.ok(result.results[2].messageId);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('sendBatch returns all failures without throwing when ALL messages fail', async () => {
|
|
208
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'batchallfail', {
|
|
209
|
+
fromAddress: 'noreply@example.com',
|
|
210
|
+
});
|
|
211
|
+
const messages = [
|
|
212
|
+
{ to: 'invalid@@', subject: 'Bad1', body: 'Body' },
|
|
213
|
+
{ to: 'also-invalid@@', subject: 'Bad2', body: 'Body' },
|
|
214
|
+
];
|
|
215
|
+
const result = await emailClient.sendBatch(messages);
|
|
216
|
+
assert.strictEqual(result.results.length, 2);
|
|
217
|
+
assert.strictEqual(result.results[0].status, 'failed');
|
|
218
|
+
assert.ok(result.results[0].error!.includes('Invalid'));
|
|
219
|
+
assert.strictEqual(result.results[1].status, 'failed');
|
|
220
|
+
assert.ok(result.results[1].error!.includes('Invalid'));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ── Batch: many messages succeeds ───────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
test('sendBatch sends many messages (no batch size limit)', async () => {
|
|
226
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'bigbatch', {
|
|
227
|
+
fromAddress: 'noreply@example.com',
|
|
228
|
+
});
|
|
229
|
+
const messages = Array.from({ length: 100 }, (_, i) => ({
|
|
230
|
+
to: `user${i}@example.com`,
|
|
231
|
+
subject: `Msg ${i}`,
|
|
232
|
+
body: `Body ${i}`,
|
|
233
|
+
}));
|
|
234
|
+
const result = await emailClient.sendBatch(messages);
|
|
235
|
+
assert.strictEqual(result.results.length, 100);
|
|
236
|
+
for (const r of result.results) {
|
|
237
|
+
assert.strictEqual(r.status, 'success');
|
|
238
|
+
assert.ok(r.messageId);
|
|
239
|
+
}
|
|
240
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-bigbatch');
|
|
241
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
242
|
+
assert.strictEqual(stored.length, 100);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('sendBatch sends all messages within small batch', async () => {
|
|
246
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'batchok', {
|
|
247
|
+
fromAddress: 'noreply@example.com',
|
|
248
|
+
});
|
|
249
|
+
const messages = Array.from({ length: 3 }, (_, i) => ({
|
|
250
|
+
to: `user${i}@example.com`,
|
|
251
|
+
subject: `Msg ${i}`,
|
|
252
|
+
body: `Body ${i}`,
|
|
253
|
+
}));
|
|
254
|
+
const result = await emailClient.sendBatch(messages);
|
|
255
|
+
assert.strictEqual(result.results.length, 3);
|
|
256
|
+
for (const r of result.results) {
|
|
257
|
+
assert.strictEqual(r.status, 'success');
|
|
258
|
+
}
|
|
259
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-batchok');
|
|
260
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
261
|
+
assert.strictEqual(stored.length, 3);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ── File persistence ────────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
test('emails persist across instances', async () => {
|
|
267
|
+
const emailClient1 = new EmailClient({ id: 'root' } as any, 'persist', {
|
|
268
|
+
fromAddress: 'noreply@example.com',
|
|
269
|
+
});
|
|
270
|
+
await emailClient1.send({ to: 'user@example.com', subject: 'First', body: 'First body' });
|
|
271
|
+
|
|
272
|
+
// New instance with same scope path reads from disk
|
|
273
|
+
const emailClient2 = new EmailClient({ id: 'root' } as any, 'persist', {
|
|
274
|
+
fromAddress: 'noreply@example.com',
|
|
275
|
+
});
|
|
276
|
+
await emailClient2.send({ to: 'user2@example.com', subject: 'Second', body: 'Second body' });
|
|
277
|
+
|
|
278
|
+
const dataDir = join(process.cwd(), '.bb-data', 'root-persist');
|
|
279
|
+
const stored = JSON.parse(readFileSync(join(dataDir, 'emails.json'), 'utf8'));
|
|
280
|
+
assert.strictEqual(stored.length, 2);
|
|
281
|
+
assert.strictEqual(stored[0].subject, 'First');
|
|
282
|
+
assert.strictEqual(stored[1].subject, 'Second');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ── Message size limit ──────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
test('rejects messages exceeding 40 MB', async () => {
|
|
288
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'big', {
|
|
289
|
+
fromAddress: 'noreply@example.com',
|
|
290
|
+
});
|
|
291
|
+
const bigBody = 'x'.repeat(41 * 1024 * 1024);
|
|
292
|
+
await assert.rejects(
|
|
293
|
+
() => emailClient.send({ to: 'user@example.com', subject: 'Big', body: bigBody }),
|
|
294
|
+
(err: Error) => err.name === EmailErrors.SendFailed,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── Error constants ─────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
test('EmailErrors has expected constants', () => {
|
|
301
|
+
assert.strictEqual(EmailErrors.SendFailed, 'EmailSendFailedException');
|
|
302
|
+
assert.strictEqual(EmailErrors.InvalidInput, 'InvalidInputException');
|
|
303
|
+
assert.strictEqual(EmailErrors.DomainNotVerified, 'DomainNotVerifiedException');
|
|
304
|
+
assert.strictEqual(EmailErrors.AccountPaused, 'AccountSendingPausedException');
|
|
305
|
+
assert.strictEqual(EmailErrors.RateLimited, 'RateLimitedException');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// ── fullId generation ───────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
test('fullId generation with parent', () => {
|
|
311
|
+
const emailClient = new EmailClient({ id: 'parent' } as any, 'child', {
|
|
312
|
+
fromAddress: 'noreply@example.com',
|
|
313
|
+
});
|
|
314
|
+
assert.strictEqual(emailClient.fullId, 'parent-child');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ── messageId uniqueness ────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
test('each send returns a unique messageId', async () => {
|
|
320
|
+
const emailClient = new EmailClient({ id: 'root' } as any, 'unique', {
|
|
321
|
+
fromAddress: 'noreply@example.com',
|
|
322
|
+
});
|
|
323
|
+
const r1 = await emailClient.send({ to: 'user@example.com', subject: 'A', body: 'a' });
|
|
324
|
+
const r2 = await emailClient.send({ to: 'user@example.com', subject: 'B', body: 'b' });
|
|
325
|
+
assert.notStrictEqual(r1.messageId, r2.messageId);
|
|
326
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared types for Email. Imported by mock, aws, cdk, and browser entry points.
|
|
6
|
+
* This file has zero runtime dependencies — types only.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChildLogger } from '@aws-blocks/bb-logger';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration options for the Email building block instance.
|
|
12
|
+
*
|
|
13
|
+
* @param fromAddress - The verified sender email address (e.g., "noreply@example.com").
|
|
14
|
+
* @param replyTo - Optional reply-to address(es).
|
|
15
|
+
* @param configurationSet - Optional SES configuration set name for tracking.
|
|
16
|
+
*/
|
|
17
|
+
export interface EmailOptions {
|
|
18
|
+
/** The verified sender email address. */
|
|
19
|
+
fromAddress: string;
|
|
20
|
+
/** Optional reply-to address(es). */
|
|
21
|
+
replyTo?: string[];
|
|
22
|
+
/** Optional SES configuration set name for tracking/events. */
|
|
23
|
+
configurationSet?: string;
|
|
24
|
+
/** Optional logger for internal operations. When omitted, a default Logger at error level is created. */
|
|
25
|
+
logger?: ChildLogger;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A complete email message used for both `send()` and `sendBatch()`.
|
|
30
|
+
*
|
|
31
|
+
* @param to - Recipient email address(es).
|
|
32
|
+
* @param subject - The email subject line.
|
|
33
|
+
* @param body - Plain text body content.
|
|
34
|
+
* @param html - Optional HTML body content.
|
|
35
|
+
* @param cc - Optional CC recipient address(es).
|
|
36
|
+
* @param bcc - Optional BCC recipient address(es).
|
|
37
|
+
*/
|
|
38
|
+
export interface EmailMessage {
|
|
39
|
+
/** Recipient email address(es). */
|
|
40
|
+
to: string | string[];
|
|
41
|
+
/** The email subject line. */
|
|
42
|
+
subject: string;
|
|
43
|
+
/** Plain text body content. */
|
|
44
|
+
body: string;
|
|
45
|
+
/** Optional HTML body content. */
|
|
46
|
+
html?: string;
|
|
47
|
+
/** Optional CC recipient address(es). */
|
|
48
|
+
cc?: string[];
|
|
49
|
+
/** Optional BCC recipient address(es). */
|
|
50
|
+
bcc?: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Result of a `send()` operation.
|
|
55
|
+
*
|
|
56
|
+
* @param messageId - The SES message ID for the sent email.
|
|
57
|
+
*/
|
|
58
|
+
export interface SendResult {
|
|
59
|
+
/** The SES message ID for the sent email. */
|
|
60
|
+
messageId: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Result of a `sendBatch()` operation with per-entry status.
|
|
65
|
+
*
|
|
66
|
+
* The `results` array is in the same order as the input `messages` array,
|
|
67
|
+
* so callers can correlate each result to its corresponding input message by index.
|
|
68
|
+
*
|
|
69
|
+
* @param results - Array of per-message results matching input order.
|
|
70
|
+
*/
|
|
71
|
+
export interface SendBatchResult {
|
|
72
|
+
/** Per-message results in the same order as the input messages array. */
|
|
73
|
+
results: Array<{
|
|
74
|
+
/** Whether this message was sent successfully or failed permanently. */
|
|
75
|
+
status: 'success' | 'failed';
|
|
76
|
+
/** The SES message ID, present when status is 'success'. */
|
|
77
|
+
messageId?: string;
|
|
78
|
+
/** Error description, present when status is 'failed'. */
|
|
79
|
+
error?: string;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
package/src/version.ts
ADDED