@molecule/api-emails-ses 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +304 -0
  2. package/package.json +7 -6
package/README.md ADDED
@@ -0,0 +1,304 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:48:05.373Z
7
+ -->
8
+
9
+ # @molecule/api-emails-ses
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ AWS SES email provider for molecule.dev.
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { setTransport } from '@molecule/api-emails'
21
+ import { provider } from '@molecule/api-emails-ses'
22
+
23
+ setTransport(provider)
24
+ ```
25
+
26
+ ## Type
27
+
28
+ `provider`
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @molecule/api-emails-ses @aws-sdk/client-sesv2 @aws-sdk/credential-provider-node @molecule/api-bond @molecule/api-emails @molecule/api-secrets nodemailer
34
+ npm install -D @types/nodemailer
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### Interfaces
40
+
41
+ #### `EmailMessage`
42
+
43
+ Email message options.
44
+
45
+ ```typescript
46
+ interface EmailMessage {
47
+ /**
48
+ * Sender address.
49
+ */
50
+ from: string | EmailAddress
51
+ /**
52
+ * Recipient(s).
53
+ */
54
+ to: string | EmailAddress | (string | EmailAddress)[]
55
+ /**
56
+ * CC recipient(s).
57
+ */
58
+ cc?: string | EmailAddress | (string | EmailAddress)[]
59
+ /**
60
+ * BCC recipient(s).
61
+ */
62
+ bcc?: string | EmailAddress | (string | EmailAddress)[]
63
+ /**
64
+ * Reply-to address.
65
+ */
66
+ replyTo?: string | EmailAddress
67
+ /**
68
+ * Email subject.
69
+ */
70
+ subject: string
71
+ /**
72
+ * Plain text body.
73
+ */
74
+ text?: string
75
+ /**
76
+ * HTML body.
77
+ */
78
+ html?: string
79
+ /**
80
+ * File attachments.
81
+ */
82
+ attachments?: EmailAttachment[]
83
+ /**
84
+ * i18n key for the subject (for client-side translation).
85
+ */
86
+ subjectKey?: string
87
+ /**
88
+ * i18n key for the plain text body (for client-side translation).
89
+ */
90
+ textKey?: string
91
+ /**
92
+ * i18n key for the HTML body (for client-side translation).
93
+ */
94
+ htmlKey?: string
95
+ }
96
+ ```
97
+
98
+ #### `EmailSendResult`
99
+
100
+ Result of sending an email.
101
+
102
+ ```typescript
103
+ interface EmailSendResult {
104
+ /**
105
+ * Whether the email was accepted for delivery.
106
+ */
107
+ accepted: string[]
108
+ /**
109
+ * Addresses that were rejected.
110
+ */
111
+ rejected: string[]
112
+ /**
113
+ * Message ID from the provider.
114
+ */
115
+ messageId?: string
116
+ /**
117
+ * Raw response from the provider.
118
+ */
119
+ response?: string
120
+ }
121
+ ```
122
+
123
+ #### `EmailTransport`
124
+
125
+ Email transport interface.
126
+
127
+ All email providers must implement this interface.
128
+
129
+ ```typescript
130
+ interface EmailTransport {
131
+ /**
132
+ * Sends an email message.
133
+ * @returns The send result.
134
+ */
135
+ sendMail(message: EmailMessage): Promise<EmailSendResult>
136
+ }
137
+ ```
138
+
139
+ ### Functions
140
+
141
+ #### `getSesClient()`
142
+
143
+ Returns the AWS SESv2 client, constructing it from the environment on the
144
+ FIRST call and memoizing thereafter.
145
+
146
+ Construction is deferred to first use — NOT module load — so a region resolved
147
+ into `process.env` AFTER this module is imported (late secrets resolution via
148
+ a secrets bond) is honored: `AWS_SES_REGION` (default `us-east-1`) and the
149
+ optional `AWS_SES_ENDPOINT` are read at send time, not frozen at import.
150
+ Reading them at import instead pinned an empty/default region and every send
151
+ failed in the WRONG region ("Email address is not verified"). Credentials
152
+ still resolve lazily via the AWS default chain (`AWS_ACCESS_KEY_ID`/
153
+ `AWS_SECRET_ACCESS_KEY`, shared config, or an instance role) at send time, so
154
+ a missing credential surfaces then as a descriptive AWS SDK error.
155
+
156
+ nodemailer 7 requires the SESv2 client + `SendEmailCommand` pair — the old
157
+ `{ ses, aws }` (@aws-sdk/client-ses) shape made `createTransport` THROW
158
+ ("legacy SES configuration"), breaking every real consumer of this bond.
159
+
160
+ ```typescript
161
+ function getSesClient(): SESv2Client
162
+ ```
163
+
164
+ **Returns:** The configured SESv2 client.
165
+
166
+ #### `sendMail(message)`
167
+
168
+ Sends an email through AWS SES via nodemailer. The SES client and transport
169
+ are configured lazily from the environment on the first call, so late-resolved
170
+ region/credentials are honored.
171
+
172
+ ```typescript
173
+ function sendMail(message: EmailMessage): Promise<EmailSendResult>
174
+ ```
175
+
176
+ - `message` — The email message (to, from, subject, text/html, attachments).
177
+
178
+ **Returns:** Send result with accepted/rejected addresses and message ID.
179
+
180
+ ### Constants
181
+
182
+ #### `email` _(deprecated)_
183
+
184
+ Raw nodemailer transport alias.
185
+
186
+ ```typescript
187
+ const email: { sendMail: (msg: nodemailer.SendMailOptions) => Promise<any> }
188
+ ```
189
+
190
+ #### `emailsSesSecretDefinitions`
191
+
192
+ Secret definitions required by the AWS SES email bond.
193
+
194
+ ```typescript
195
+ const emailsSesSecretDefinitions: SecretDefinition[]
196
+ ```
197
+
198
+ #### `provider`
199
+
200
+ The SES email provider implementing the `EmailTransport` interface.
201
+
202
+ ```typescript
203
+ const provider: EmailTransport
204
+ ```
205
+
206
+ #### `transport` _(deprecated)_
207
+
208
+ Raw nodemailer transport for direct access. Lazily configured on first send.
209
+
210
+ ```typescript
211
+ const transport: { sendMail: (msg: nodemailer.SendMailOptions) => Promise<any> }
212
+ ```
213
+
214
+ ## Core Interface
215
+
216
+ Implements `@molecule/api-emails` interface.
217
+
218
+ ## Bond Wiring
219
+
220
+ Setup function to register this provider with the core interface:
221
+
222
+ ```typescript
223
+ import { setTransport } from '@molecule/api-emails'
224
+ import { provider } from '@molecule/api-emails-ses'
225
+
226
+ export function setupEmailsSes(): void {
227
+ setTransport(provider)
228
+ }
229
+ ```
230
+
231
+ ## Injection Notes
232
+
233
+ ### Requirements
234
+
235
+ Peer dependencies:
236
+
237
+ - `@molecule/api-bond` ^1.0.1
238
+ - `@molecule/api-emails` ^1.0.1
239
+ - `@molecule/api-secrets` ^1.0.1
240
+
241
+ ### Environment Variables
242
+
243
+ - `AWS_ACCESS_KEY_ID` _(required)_ — AWS access key ID
244
+ - Setup: Create an IAM user with the needed policy (SES/S3/SQS) and create an access key under Security credentials.
245
+ - Get it here: [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)
246
+ - Example: `AKIA...`
247
+ - `AWS_SECRET_ACCESS_KEY` _(required)_ — AWS secret access key
248
+ - Setup: Shown once when creating the IAM access key — store it immediately.
249
+ - Get it here: [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)
250
+ - `AWS_SES_REGION` _(required)_ — AWS SES region
251
+ - Setup: The AWS region where SES is set up (and out of sandbox for production sending).
252
+ - Example: `us-east-1`
253
+
254
+ ### Runtime Dependencies
255
+
256
+ - `@aws-sdk/client-sesv2`
257
+ - `@aws-sdk/credential-provider-node`
258
+ - `@molecule/api-bond`
259
+ - `@molecule/api-emails`
260
+ - `@molecule/api-secrets`
261
+ - `nodemailer`
262
+
263
+ - **Configuration is lazy and env-driven**: the SES client is constructed on
264
+ the FIRST send — NOT at import — so `AWS_SES_REGION` (default `us-east-1`)
265
+ and the optional `AWS_SES_ENDPOINT` are read at send time. A region resolved
266
+ into env AFTER this module is imported (late secrets resolution via a
267
+ secrets bond) is honored; reading it at import instead froze the
268
+ default/empty region and sends failed in the WRONG region ("Email address
269
+ is not verified"). Credentials resolve lazily via the AWS default chain
270
+ (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, shared config, or an instance
271
+ role), so they may arrive after import too.
272
+ - **No fail-fast**: because credentials can legitimately come from an instance
273
+ role or shared config (not env), missing credentials are not pre-checked —
274
+ they surface at first send as a descriptive AWS SDK error ("Could not load
275
+ credentials…"), not a tagged config error naming the env var.
276
+ - New SES accounts are sandboxed: both the sender identity AND every
277
+ recipient must be verified until production access is granted.
278
+ - On success `accepted` is mapped from `envelope.to` — nodemailer's SES
279
+ transport never sets `accepted`/`rejected` (the `@types/nodemailer` typings
280
+ claiming otherwise are drift); a resolved send means SES accepted the
281
+ message for every envelope recipient.
282
+
283
+ ## E2E Tests
284
+
285
+ Integration checklist — drive the real UI (live preview, no mocks). The
286
+ sandbox CAPTURES outbound email instead of sending — read each message with
287
+ the `read_activity` tool (filter type 'email'); the verification/reset link
288
+ is in its payload. Never mock the send or modify production code to expose
289
+ it. Adapt each item to this app's actual screens/flows, and check every box
290
+ off one by one. A box you can't check is an integration bug to fix — not a
291
+ skip:
292
+
293
+ - [ ] Each email-triggering flow (signup verification, password-reset request,
294
+ invites/notifications the app defines) confirms the send in the UI ("check
295
+ your inbox") and a message actually reaches the transport.
296
+ - [ ] The password-reset round-trip completes: request a reset → open the
297
+ captured message → follow its single-use link → set a new password → log
298
+ in with it (and the old password no longer works).
299
+ - [ ] The message body contains a LINK, never the raw token/secret, and renders
300
+ with the app's real name/content (no `undefined` placeholders).
301
+ - [ ] Requesting a reset for an unknown email shows the same neutral UI response
302
+ as a known one (no account-existence oracle).
303
+ - [ ] Account emails go only to the account's own address — no UI or endpoint
304
+ lets an unauthenticated caller send to an arbitrary address.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-emails-ses",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AWS SES email provider for molecule.dev.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,8 @@
17
17
  }
18
18
  },
19
19
  "files": [
20
- "dist"
20
+ "dist",
21
+ "README.md"
21
22
  ],
22
23
  "keywords": [
23
24
  "molecule",
@@ -33,16 +34,16 @@
33
34
  "nodemailer": "9.0.3"
34
35
  },
35
36
  "devDependencies": {
36
- "@molecule/api-bond": "1.0.0",
37
+ "@molecule/api-bond": "1.0.1",
37
38
  "@types/node": "26.1.2",
38
39
  "@types/nodemailer": "8.0.1",
39
40
  "typescript": "6.0.3",
40
41
  "vitest": "4.1.10"
41
42
  },
42
43
  "peerDependencies": {
43
- "@molecule/api-bond": "^1.0.0",
44
- "@molecule/api-emails": "^1.0.0",
45
- "@molecule/api-secrets": "^1.0.0"
44
+ "@molecule/api-bond": "^1.0.1",
45
+ "@molecule/api-emails": "^1.0.1",
46
+ "@molecule/api-secrets": "^1.0.1"
46
47
  },
47
48
  "repository": {
48
49
  "type": "git",