@fmontoya/aws-ses-adapter 1.0.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabian Montoya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,444 @@
1
+ # @fmontoya/aws-ses-adapter
2
+
3
+ A lightweight TypeScript adapter that simplifies the [AWS SES](https://aws.amazon.com/ses/) email sending API for Node.js.
4
+
5
+ - **Zero-boilerplate** — one `init()` call, then just `sendEmail()`
6
+ - **Attachments built-in** — multipart MIME constructed for you automatically
7
+ - **Typed errors** — four distinct error classes for precise error handling
8
+ - **ESM + CJS** — works in both module systems out of the box
9
+ - **Env-var fallbacks** — credentials can come from environment variables
10
+
11
+ ## Table of Contents
12
+
13
+ - [@fmontoya/aws-ses-adapter](#fmontoyaaws-ses-adapter)
14
+ - [Table of Contents](#table-of-contents)
15
+ - [Installation](#installation)
16
+ - [Prerequisites](#prerequisites)
17
+ - [Quick Start](#quick-start)
18
+ - [1. Initialize once (at application startup)](#1-initialize-once-at-application-startup)
19
+ - [2. Send emails anywhere in your app](#2-send-emails-anywhere-in-your-app)
20
+ - [Configuration](#configuration)
21
+ - [Via `init()` options](#via-init-options)
22
+ - [Via environment variables](#via-environment-variables)
23
+ - [API Reference](#api-reference)
24
+ - [`init(config?)`](#initconfig)
25
+ - [`sendEmail(options)`](#sendemailoptions)
26
+ - [`sendEmailWithAttachments(options)`](#sendemailwithattachmentsoptions)
27
+ - [`sendRawEmail(rawMessage)`](#sendrawemailrawmessage)
28
+ - [Utility functions](#utility-functions)
29
+ - [`SesAdapter` class](#sesadapter-class)
30
+ - [Error Handling](#error-handling)
31
+ - [Usage Examples](#usage-examples)
32
+ - [Express.js](#expressjs)
33
+ - [NestJS](#nestjs)
34
+ - [TypeScript Types](#typescript-types)
35
+ - [License](#license)
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ # npm
43
+ npm install @fmontoya/aws-ses-adapter
44
+
45
+ # pnpm
46
+ pnpm add @fmontoya/aws-ses-adapter
47
+
48
+ # yarn
49
+ yarn add @fmontoya/aws-ses-adapter
50
+ ```
51
+
52
+ > **Requires Node.js 20 or later.**
53
+
54
+ ---
55
+
56
+ ## Prerequisites
57
+
58
+ 1. An [AWS account](https://aws.amazon.com/) with SES enabled in your chosen region.
59
+ 2. A **verified sender identity** (domain or email address) in SES.
60
+ 3. AWS credentials with at least the `ses:SendEmail` and `ses:SendRawEmail` IAM permissions.
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ ### 1. Initialize once (at application startup)
67
+
68
+ ```ts
69
+ import { init } from '@fmontoya/aws-ses-adapter';
70
+
71
+ init({
72
+ region: 'us-east-1',
73
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
74
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
75
+ defaultFrom: 'noreply@example.com',
76
+ });
77
+ ```
78
+
79
+ ### 2. Send emails anywhere in your app
80
+
81
+ ```ts
82
+ import { sendEmail } from '@fmontoya/aws-ses-adapter';
83
+
84
+ const result = await sendEmail({
85
+ to: 'user@example.com',
86
+ subject: 'Welcome!',
87
+ html: '<h1>Welcome to our platform!</h1>',
88
+ text: 'Welcome to our platform!',
89
+ });
90
+
91
+ console.log('Sent! Message ID:', result.messageId);
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Configuration
97
+
98
+ ### Via `init()` options
99
+
100
+ | Option | Type | Required | Description |
101
+ | ----------------- | -------- | -------- | ------------------------------------------------------------- |
102
+ | `region` | `string` | Yes\* | AWS region where SES is configured (e.g. `us-east-1`). |
103
+ | `accessKeyId` | `string` | Yes\* | AWS access key ID. |
104
+ | `secretAccessKey` | `string` | Yes\* | AWS secret access key. |
105
+ | `defaultFrom` | `string` | No | Default "From" address used when `from` is omitted per-email. |
106
+
107
+ \* Required unless the corresponding environment variable is set.
108
+
109
+ ### Via environment variables
110
+
111
+ If a field is not provided to `init()`, the adapter automatically falls back to these environment variables:
112
+
113
+ | Variable | Corresponds to |
114
+ | ----------------------- | ----------------- |
115
+ | `AWS_SES_REGION` | `region` |
116
+ | `AWS_ACCESS_KEY_ID` | `accessKeyId` |
117
+ | `AWS_SECRET_ACCESS_KEY` | `secretAccessKey` |
118
+ | `AWS_SES_FROM_EMAIL` | `defaultFrom` |
119
+
120
+ Calling `init()` with no arguments will rely entirely on environment variables:
121
+
122
+ ```ts
123
+ // All credentials are read from environment variables
124
+ init();
125
+ ```
126
+
127
+ ---
128
+
129
+ ## API Reference
130
+
131
+ ### `init(config?)`
132
+
133
+ Initializes the adapter singleton. Must be called **once** before any send function.
134
+
135
+ ```ts
136
+ import { init } from '@fmontoya/aws-ses-adapter';
137
+
138
+ init({
139
+ region: 'us-east-1',
140
+ accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
141
+ secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
142
+ defaultFrom: 'noreply@example.com',
143
+ });
144
+ ```
145
+
146
+ Calling `init()` again replaces the existing singleton — useful for reconfiguration in tests.
147
+
148
+ **Throws:** `SesConfigError` when required credentials cannot be resolved.
149
+
150
+ ---
151
+
152
+ ### `sendEmail(options)`
153
+
154
+ Sends a standard HTML and/or plain-text email.
155
+
156
+ ```ts
157
+ const result = await sendEmail({
158
+ to: 'alice@example.com', // string or string[]
159
+ subject: 'Hello Alice',
160
+ html: '<p>Hi Alice!</p>',
161
+ text: 'Hi Alice!',
162
+ from: 'sender@example.com', // optional — overrides defaultFrom
163
+ replyTo: 'support@example.com', // optional — string or string[]
164
+ cc: ['manager@example.com'], // optional — string or string[]
165
+ bcc: 'audit@example.com', // optional — string or string[]
166
+ });
167
+ ```
168
+
169
+ **Options (`SendEmailOptions`):**
170
+
171
+ | Field | Type | Required | Description |
172
+ | --------- | -------------------- | -------- | --------------------------------------------------------- |
173
+ | `to` | `string \| string[]` | Yes | Recipient(s). |
174
+ | `subject` | `string` | Yes | Email subject line. |
175
+ | `html` | `string` | Yes\* | HTML body. |
176
+ | `text` | `string` | Yes\* | Plain-text body. |
177
+ | `from` | `string` | No | Sender address. Overrides the `defaultFrom` from `init`.. |
178
+ | `replyTo` | `string \| string[]` | No | Reply-To address(es). |
179
+ | `cc` | `string \| string[]` | No | CC address(es). |
180
+ | `bcc` | `string \| string[]` | No | BCC address(es). |
181
+
182
+ \* At least one of `html` or `text` is required.
183
+
184
+ **Returns:** `Promise<SendEmailResult>`
185
+
186
+ **Throws:** `SesNotInitializedError` | `SesValidationError` | `SesSendError`
187
+
188
+ ---
189
+
190
+ ### `sendEmailWithAttachments(options)`
191
+
192
+ Sends an email with one or more file attachments. The MIME message is constructed automatically.
193
+
194
+ ```ts
195
+ import { sendEmailWithAttachments } from '@fmontoya/aws-ses-adapter';
196
+ import { readFileSync } from 'fs';
197
+
198
+ const result = await sendEmailWithAttachments({
199
+ to: 'alice@example.com',
200
+ subject: 'Your invoice',
201
+ html: '<p>Please find the invoice attached.</p>',
202
+ attachments: [
203
+ {
204
+ filename: 'invoice.pdf',
205
+ content: readFileSync('./invoice.pdf'), // Buffer or string
206
+ contentType: 'application/pdf',
207
+ },
208
+ {
209
+ filename: 'notes.txt',
210
+ content: 'Some plain text notes.',
211
+ contentType: 'text/plain',
212
+ },
213
+ ],
214
+ });
215
+ ```
216
+
217
+ **Additional field (`EmailAttachment`):**
218
+
219
+ | Field | Type | Required | Description |
220
+ | ------------- | ------------------ | -------- | ------------------------------------------------ |
221
+ | `filename` | `string` | Yes | Filename shown to the recipient. |
222
+ | `content` | `Buffer \| string` | Yes | File content. Use `Buffer` for binary files. |
223
+ | `contentType` | `string` | Yes | MIME type (e.g. `application/pdf`, `image/png`). |
224
+
225
+ **Throws:** `SesNotInitializedError` | `SesValidationError` | `SesSendError`
226
+
227
+ ---
228
+
229
+ ### `sendRawEmail(rawMessage)`
230
+
231
+ Sends a fully-formed raw MIME message when you need complete control over the email format.
232
+
233
+ ```ts
234
+ import { sendRawEmail } from '@fmontoya/aws-ses-adapter';
235
+
236
+ const mime = [
237
+ 'From: sender@example.com',
238
+ 'To: recipient@example.com',
239
+ 'Subject: Raw MIME email',
240
+ 'MIME-Version: 1.0',
241
+ 'Content-Type: text/plain; charset=UTF-8',
242
+ '',
243
+ 'Hello from a raw MIME message.',
244
+ ].join('\r\n');
245
+
246
+ const result = await sendRawEmail(mime);
247
+ ```
248
+
249
+ **Throws:** `SesNotInitializedError` | `SesValidationError` | `SesSendError`
250
+
251
+ ---
252
+
253
+ ### Utility functions
254
+
255
+ ```ts
256
+ import {
257
+ isInitialized,
258
+ hasDefaultFrom,
259
+ getDefaultFrom,
260
+ getRegion,
261
+ } from '@fmontoya/aws-ses-adapter';
262
+
263
+ isInitialized(); // boolean — true if init() has been called
264
+ hasDefaultFrom(); // boolean — true if a defaultFrom address is configured
265
+ getDefaultFrom(); // string | undefined — the configured defaultFrom address
266
+ getRegion(); // string — the configured AWS region, e.g. 'us-east-1'
267
+ ```
268
+
269
+ ---
270
+
271
+ ### `SesAdapter` class
272
+
273
+ For advanced use cases — such as managing multiple independent instances or building framework integrations (e.g. NestJS modules) — you can instantiate `SesAdapter` directly:
274
+
275
+ ```ts
276
+ import { SesAdapter } from '@fmontoya/aws-ses-adapter';
277
+
278
+ const adapter = new SesAdapter({
279
+ region: 'eu-west-1',
280
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
281
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
282
+ defaultFrom: 'noreply@myapp.com',
283
+ });
284
+
285
+ await adapter.sendEmail({
286
+ to: 'user@example.com',
287
+ subject: 'Hello!',
288
+ html: '<p>Hello from a direct instance.</p>',
289
+ });
290
+ ```
291
+
292
+ The class exposes the same methods as the singleton API: `sendEmail()`, `sendEmailWithAttachments()`, `sendRawEmail()`, `hasDefaultFrom()`, `getDefaultFrom()`, and `getRegion()`.
293
+
294
+ ---
295
+
296
+ ## Error Handling
297
+
298
+ All errors extend the native `Error` class and carry a stable `name` property for programmatic identification.
299
+
300
+ | Error class | When it is thrown |
301
+ | ------------------------ | --------------------------------------------------------------------- |
302
+ | `SesNotInitializedError` | A send function is called before `init()`. |
303
+ | `SesConfigError` | `init()` is called with missing/invalid credentials. |
304
+ | `SesValidationError` | Required email fields are missing (e.g. no `html`/`text`, no `from`). |
305
+ | `SesSendError` | The AWS SES API call fails. The original error is in `err.cause`. |
306
+
307
+ ```ts
308
+ import {
309
+ sendEmail,
310
+ SesNotInitializedError,
311
+ SesConfigError,
312
+ SesValidationError,
313
+ SesSendError,
314
+ } from '@fmontoya/aws-ses-adapter';
315
+
316
+ try {
317
+ await sendEmail({
318
+ to: 'user@example.com',
319
+ subject: 'Hi',
320
+ html: '<p>Hello</p>',
321
+ });
322
+ } catch (err) {
323
+ if (err instanceof SesNotInitializedError) {
324
+ console.error('Call init() before sending emails.');
325
+ } else if (err instanceof SesConfigError) {
326
+ console.error('Invalid configuration:', err.message);
327
+ } else if (err instanceof SesValidationError) {
328
+ console.error('Invalid email options:', err.message);
329
+ } else if (err instanceof SesSendError) {
330
+ console.error('AWS SES send failed:', err.message);
331
+ console.error('Root cause:', err.cause);
332
+ }
333
+ }
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Usage Examples
339
+
340
+ ### Express.js
341
+
342
+ ```ts
343
+ // app.ts
344
+ import express from 'express';
345
+ import { init } from '@fmontoya/aws-ses-adapter';
346
+
347
+ init(); // reads credentials from environment variables
348
+
349
+ const app = express();
350
+ app.use(express.json());
351
+
352
+ app.listen(3000);
353
+ ```
354
+
355
+ ```ts
356
+ // routes/contact.ts
357
+ import { Router } from 'express';
358
+ import { sendEmail } from '@fmontoya/aws-ses-adapter';
359
+
360
+ const router = Router();
361
+
362
+ router.post('/contact', async (req, res) => {
363
+ const { name, email, message } = req.body;
364
+
365
+ await sendEmail({
366
+ to: 'support@example.com',
367
+ subject: `Contact form submission from ${name}`,
368
+ html: `<p><strong>From:</strong> ${email}</p><p>${message}</p>`,
369
+ text: `From: ${email}\n\n${message}`,
370
+ replyTo: email,
371
+ });
372
+
373
+ res.json({ ok: true });
374
+ });
375
+
376
+ export default router;
377
+ ```
378
+
379
+ ### NestJS
380
+
381
+ Create a provider using the `SesAdapter` class directly so it integrates with NestJS's dependency injection:
382
+
383
+ ```ts
384
+ // email/email.module.ts
385
+ import { Module } from '@nestjs/common';
386
+ import { EmailService } from './email.service';
387
+
388
+ @Module({
389
+ providers: [
390
+ {
391
+ provide: 'SES_ADAPTER',
392
+ useFactory: () => {
393
+ const { SesAdapter } = require('@fmontoya/aws-ses-adapter');
394
+ return new SesAdapter({
395
+ region: process.env.AWS_SES_REGION,
396
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
397
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
398
+ defaultFrom: process.env.AWS_SES_FROM_EMAIL,
399
+ });
400
+ },
401
+ },
402
+ EmailService,
403
+ ],
404
+ exports: [EmailService],
405
+ })
406
+ export class EmailModule {}
407
+ ```
408
+
409
+ ```ts
410
+ // email/email.service.ts
411
+ import { Inject, Injectable } from '@nestjs/common';
412
+ import type { SesAdapter, SendEmailOptions } from '@fmontoya/aws-ses-adapter';
413
+
414
+ @Injectable()
415
+ export class EmailService {
416
+ constructor(@Inject('SES_ADAPTER') private readonly ses: SesAdapter) {}
417
+
418
+ async send(options: SendEmailOptions) {
419
+ return this.ses.sendEmail(options);
420
+ }
421
+ }
422
+ ```
423
+
424
+ ---
425
+
426
+ ## TypeScript Types
427
+
428
+ All public types are exported from the package root:
429
+
430
+ ```ts
431
+ import type {
432
+ SesAdapterConfig,
433
+ SendEmailOptions,
434
+ SendEmailWithAttachmentsOptions,
435
+ EmailAttachment,
436
+ SendEmailResult,
437
+ } from '@fmontoya/aws-ses-adapter';
438
+ ```
439
+
440
+ ---
441
+
442
+ ## License
443
+
444
+ [MIT](./LICENSE) © [FabianMontoya](https://github.com/FabianMontoya)