@molecule/api-emails-mailgun 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.
- package/README.md +277 -0
- package/package.json +7 -6
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
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:04.181Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @molecule/api-emails-mailgun
|
|
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
|
+
Mailgun 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-mailgun'
|
|
22
|
+
|
|
23
|
+
setTransport(provider)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Type
|
|
27
|
+
|
|
28
|
+
`provider`
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @molecule/api-emails-mailgun @molecule/api-bond @molecule/api-emails @molecule/api-secrets nodemailer nodemailer-mailgun-transport
|
|
34
|
+
npm install -D @types/nodemailer @types/nodemailer-mailgun-transport
|
|
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
|
+
#### `sendMail(message)`
|
|
142
|
+
|
|
143
|
+
Sends an email message via the Mailgun API, with automatic test-mode handling for sandbox domains.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
function sendMail(message: EmailMessage): Promise<EmailSendResult>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `message` — The email message (to, from, subject, text/html, attachments).
|
|
150
|
+
|
|
151
|
+
**Returns:** Send result with accepted/rejected addresses and message ID.
|
|
152
|
+
|
|
153
|
+
### Constants
|
|
154
|
+
|
|
155
|
+
#### `email` _(deprecated)_
|
|
156
|
+
|
|
157
|
+
Raw nodemailer transport alias.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const email: { sendMail: (msg: nodemailer.SendMailOptions) => Promise<any> }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `mailgunSecretDefinitions`
|
|
164
|
+
|
|
165
|
+
Secret definitions required by the Mailgun email bond.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const mailgunSecretDefinitions: SecretDefinition[]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### `provider`
|
|
172
|
+
|
|
173
|
+
The Mailgun email provider implementing the `EmailTransport` interface.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const provider: EmailTransport
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `transport` _(deprecated)_
|
|
180
|
+
|
|
181
|
+
Raw nodemailer transport for direct access.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const transport: { sendMail: (msg: nodemailer.SendMailOptions) => Promise<any> }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Core Interface
|
|
188
|
+
|
|
189
|
+
Implements `@molecule/api-emails` interface.
|
|
190
|
+
|
|
191
|
+
## Bond Wiring
|
|
192
|
+
|
|
193
|
+
Setup function to register this provider with the core interface:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { setTransport } from '@molecule/api-emails'
|
|
197
|
+
import { provider } from '@molecule/api-emails-mailgun'
|
|
198
|
+
|
|
199
|
+
export function setupEmailsMailgun(): void {
|
|
200
|
+
setTransport(provider)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Injection Notes
|
|
205
|
+
|
|
206
|
+
### Requirements
|
|
207
|
+
|
|
208
|
+
Peer dependencies:
|
|
209
|
+
|
|
210
|
+
- `@molecule/api-bond` ^1.0.1
|
|
211
|
+
- `@molecule/api-emails` ^1.0.1
|
|
212
|
+
- `@molecule/api-secrets` ^1.0.1
|
|
213
|
+
|
|
214
|
+
### Environment Variables
|
|
215
|
+
|
|
216
|
+
- `MAILGUN_API_KEY` _(required)_ — Mailgun API key
|
|
217
|
+
- Setup: Mailgun dashboard → Settings → API Security → create/copy a sending API key.
|
|
218
|
+
- Get it here: [https://app.mailgun.com/settings/api_security](https://app.mailgun.com/settings/api_security)
|
|
219
|
+
- `MAILGUN_DOMAIN` _(required)_ — Mailgun sending domain
|
|
220
|
+
- Setup: Add and verify a sending domain in Mailgun (sandbox domains work for testing to authorized recipients).
|
|
221
|
+
- Get it here: [https://app.mailgun.com/mg/sending/domains](https://app.mailgun.com/mg/sending/domains)
|
|
222
|
+
- Example: `mg.example.com`
|
|
223
|
+
- `MAILGUN_API_HOST` _(optional)_ — Mailgun API host
|
|
224
|
+
- Setup: Only needed for EU-region Mailgun accounts (api.eu.mailgun.net) or a self-hosted/broker endpoint; leave unset for US-region accounts.
|
|
225
|
+
- Example: `api.eu.mailgun.net`
|
|
226
|
+
|
|
227
|
+
### Runtime Dependencies
|
|
228
|
+
|
|
229
|
+
- `@molecule/api-bond`
|
|
230
|
+
- `@molecule/api-emails`
|
|
231
|
+
- `@molecule/api-secrets`
|
|
232
|
+
- `nodemailer`
|
|
233
|
+
- `nodemailer-mailgun-transport`
|
|
234
|
+
|
|
235
|
+
- **EU-region Mailgun accounts must set `MAILGUN_API_HOST=api.eu.mailgun.net`**
|
|
236
|
+
(optional env; defaults to Mailgun's US endpoint). Without it every send
|
|
237
|
+
fails upstream with 401 even though the key is valid — wrong region, not
|
|
238
|
+
wrong key.
|
|
239
|
+
- **Sandbox domains auto-enable Mailgun test mode**: when `MAILGUN_DOMAIN`
|
|
240
|
+
matches `sandbox*.mailgun.org` (or `MAILGUN_TEST_MODE=true`), sends carry
|
|
241
|
+
`o:testmode=yes` — Mailgun accepts, validates, and assigns a message id
|
|
242
|
+
but NEVER delivers. A sandbox 403 for an unauthorized recipient is
|
|
243
|
+
reported as a synthetic success (`response: 'sandbox-test-mode'`). "Send
|
|
244
|
+
succeeded but no email arrived" in dev is this behavior, not a bug.
|
|
245
|
+
- Credentials are read lazily on first send and fail fast with a tagged
|
|
246
|
+
`config.notConfigured` error naming the missing key (`MAILGUN_API_KEY`,
|
|
247
|
+
then `MAILGUN_DOMAIN`). On success `accepted` echoes the message's own
|
|
248
|
+
recipients (Mailgun's transport returns no per-recipient verdict).
|
|
249
|
+
- **The `from` address's domain must equal `MAILGUN_DOMAIN`** (Mailgun sends
|
|
250
|
+
through, and signs SPF/DKIM for, that verified domain). A `from` on any other
|
|
251
|
+
domain — a hardcoded `noreply@example.com`, `noreply@store.com`, etc. — is
|
|
252
|
+
rejected or unsigned (spam). Default the sender to the sending domain:
|
|
253
|
+
`` process.env.EMAIL_FROM ?? `no-reply@${process.env.MAILGUN_DOMAIN}` `` — never
|
|
254
|
+
a literal placeholder domain.
|
|
255
|
+
|
|
256
|
+
## E2E Tests
|
|
257
|
+
|
|
258
|
+
Integration checklist — drive the real UI (live preview, no mocks). The
|
|
259
|
+
sandbox CAPTURES outbound email instead of sending — read each message with
|
|
260
|
+
the `read_activity` tool (filter type 'email'); the verification/reset link
|
|
261
|
+
is in its payload. Never mock the send or modify production code to expose
|
|
262
|
+
it. Adapt each item to this app's actual screens/flows, and check every box
|
|
263
|
+
off one by one. A box you can't check is an integration bug to fix — not a
|
|
264
|
+
skip:
|
|
265
|
+
|
|
266
|
+
- [ ] Each email-triggering flow (signup verification, password-reset request,
|
|
267
|
+
invites/notifications the app defines) confirms the send in the UI ("check
|
|
268
|
+
your inbox") and a message actually reaches the transport.
|
|
269
|
+
- [ ] The password-reset round-trip completes: request a reset → open the
|
|
270
|
+
captured message → follow its single-use link → set a new password → log
|
|
271
|
+
in with it (and the old password no longer works).
|
|
272
|
+
- [ ] The message body contains a LINK, never the raw token/secret, and renders
|
|
273
|
+
with the app's real name/content (no `undefined` placeholders).
|
|
274
|
+
- [ ] Requesting a reset for an unknown email shows the same neutral UI response
|
|
275
|
+
as a known one (no account-existence oracle).
|
|
276
|
+
- [ ] Account emails go only to the account's own address — no UI or endpoint
|
|
277
|
+
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-mailgun",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Mailgun 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",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"nodemailer-mailgun-transport": "2.1.5"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@molecule/api-bond": "1.0.
|
|
35
|
+
"@molecule/api-bond": "1.0.1",
|
|
35
36
|
"@types/node": "26.1.2",
|
|
36
37
|
"@types/nodemailer": "8.0.1",
|
|
37
38
|
"@types/nodemailer-mailgun-transport": "1.4.6",
|
|
@@ -39,9 +40,9 @@
|
|
|
39
40
|
"vitest": "4.1.10"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"@molecule/api-bond": "^1.0.
|
|
43
|
-
"@molecule/api-emails": "^1.0.
|
|
44
|
-
"@molecule/api-secrets": "^1.0.
|
|
43
|
+
"@molecule/api-bond": "^1.0.1",
|
|
44
|
+
"@molecule/api-emails": "^1.0.1",
|
|
45
|
+
"@molecule/api-secrets": "^1.0.1"
|
|
45
46
|
},
|
|
46
47
|
"repository": {
|
|
47
48
|
"type": "git",
|