@letsrunit/mailbox 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/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Mailbox Package (`@letsrunit/mailbox`)
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install @letsrunit/mailbox
7
+ # or
8
+ yarn add @letsrunit/mailbox
9
+ ```
10
+
11
+ Utilities for interacting with mailbox services to test email-based flows (OTP, activation links, etc.). It supports multiple backends for both local development and production environments.
12
+
13
+ ## Exported Functions
14
+
15
+ ### `getMailbox(seed, name?, domain?)`
16
+
17
+ Generates a deterministic email address based on a seed (UUID).
18
+
19
+ - **`seed`**: A UUID used to ensure the email is unique but reproducible.
20
+ - **`name`**: Optional sub-address name.
21
+ - **`domain`**: Optional domain. Defaults to the configured `MAILBOX_DOMAIN`.
22
+
23
+ ### `receiveMail(emailAddress, options)`
24
+
25
+ Retrieves emails sent to a specific address.
26
+
27
+ - **`emailAddress`**: The address to check.
28
+ - **`options`**:
29
+ - `timeout`: How long to wait for emails.
30
+ - `until`: A predicate function to stop polling when an email is found.
31
+
32
+ ### `toEml(email)`
33
+
34
+ Converts an `Email` object into a string representation in `.eml` format.
35
+
36
+ ### `fromEml(email)`
37
+
38
+ Convert a string in `.eml` from to an `Email` object.
39
+
40
+ ## Supported Services
41
+
42
+ The package automatically selects the service based on the `MAILBOX_SERVICE` environment variable.
43
+
44
+ ### [Mailpit](https://mailpit.axllent.org/)
45
+ Recommended for local development. It provides a fast, lightweight way to capture and view emails.
46
+ - **Environment Variables**: `MAILPIT_BASE_URL` (defaults to `http://localhost:8025`).
47
+
48
+ ### [Testmail.app](https://testmail.app/)
49
+ Recommended for production/GCP. It provides infinite private email addresses and a powerful GraphQL API.
50
+ - **Environment Variables**: `TESTMAIL_API_KEY`, `TESTMAIL_NAMESPACE`, `MAILBOX_DOMAIN` (optional, defaults to `inbox.testmail.app`).
51
+
52
+ ### [Mailhog](https://github.com/mailhog/MailHog)
53
+ Supported for legacy environments.
54
+ - **Environment Variables**: `MAILHOG_BASE_URL` (defaults to `http://localhost:8025`).
55
+
56
+ ## Testing
57
+
58
+ Run tests for this package:
59
+
60
+ ```bash
61
+ yarn test
62
+ ```
@@ -0,0 +1,34 @@
1
+ import { UUID } from '@letsrunit/utils';
2
+
3
+ declare function getMailbox(seed: UUID, name?: string, domain?: string): string;
4
+
5
+ interface ReceiveOptions {
6
+ wait?: boolean;
7
+ timeout?: number;
8
+ signal?: AbortSignal;
9
+ after?: number;
10
+ subject?: string;
11
+ limit?: number;
12
+ full?: boolean;
13
+ }
14
+ interface Email {
15
+ timestamp: number;
16
+ from: string;
17
+ to: string;
18
+ cc?: string;
19
+ subject: string;
20
+ html?: string;
21
+ text?: string;
22
+ attachments?: Attachment[];
23
+ }
24
+ interface Attachment {
25
+ filename: string;
26
+ contentType: string;
27
+ }
28
+
29
+ declare function receiveMail(emailAddress: string, options?: ReceiveOptions): Promise<Email[]>;
30
+
31
+ declare function toEml(email: Email): string;
32
+ declare function fromEml(contents: string): Email;
33
+
34
+ export { fromEml, getMailbox, receiveMail, toEml };
package/dist/index.js ADDED
@@ -0,0 +1,470 @@
1
+ import { clean, uuidToTag, sleep } from '@letsrunit/utils';
2
+ import { GraphQLClient } from 'graphql-request';
3
+
4
+ // src/mailbox.ts
5
+
6
+ // src/constants.ts
7
+ var testValue = (value) => process.env.NODE_ENV === "test" ? value : null;
8
+ var TESTMAIL_DOMAIN = "inbox.testmail.app";
9
+ var TESTMAIL_API_KEY = process.env.TESTMAIL_API_KEY || testValue("test_key");
10
+ var TESTMAIL_NAMESPACE = process.env.TESTMAIL_NAMESPACE || testValue("test_ns");
11
+ var TESTMAIL_GRAPHQL_URL = "https://api.testmail.app/api/graphql";
12
+ var MAILHOG_BASE_URL = process.env.MAILHOG_BASE_URL || "http://localhost:8025";
13
+ var MAILPIT_BASE_URL = process.env.MAILPIT_BASE_URL || "http://localhost:8025";
14
+ var MAILBOX_SERVICE = process.env.MAILBOX_SERVICE || "mailpit";
15
+ var MAILBOX_DOMAIN = process.env.MAILBOX_DOMAIN || (MAILBOX_SERVICE === "testmail" ? TESTMAIL_DOMAIN : "example.com");
16
+
17
+ // src/mailbox.ts
18
+ function getMailbox(seed, name, domain) {
19
+ domain ??= MAILBOX_DOMAIN;
20
+ const ns = domain === TESTMAIL_DOMAIN ? TESTMAIL_NAMESPACE : null;
21
+ const local = clean([ns, uuidToTag(seed), name]).join(".");
22
+ return `${local}@${domain}`;
23
+ }
24
+ async function fetchOnce(emailAddress, signal) {
25
+ const url = `${MAILHOG_BASE_URL.replace(/\/$/, "")}/api/v2/search?kind=to&query=${encodeURIComponent(emailAddress)}`;
26
+ const res = await fetch(url, { signal });
27
+ if (!res.ok) {
28
+ const text = await res.text();
29
+ throw new Error(`Failed to fetch response from mailhog: ${res.status} ${text}`);
30
+ }
31
+ const body = await res.json();
32
+ return body?.items ?? body?.Items ?? [];
33
+ }
34
+ function getHeaderValue(headers, name) {
35
+ if (!headers) return void 0;
36
+ const v = headers[name] ?? headers[name.toLowerCase()];
37
+ if (!v) return void 0;
38
+ return Array.isArray(v) ? v[0] : v;
39
+ }
40
+ function mapItemsToEmails(items) {
41
+ return items.map((item) => {
42
+ const created = item.Created || item.created || item.Time;
43
+ const timestamp = typeof created === "number" ? created : Date.parse(created);
44
+ const headers = item.Content?.Headers || item.Content?.headers;
45
+ const parts = item.MIME?.Parts || item.MIME?.parts || [];
46
+ const htmlPart = parts.find((p) => (p.ContentType || p.contentType || "").startsWith("text/html"));
47
+ const textPart = parts.find((p) => (p.ContentType || p.contentType || "").startsWith("text/plain"));
48
+ const html = htmlPart?.Body || htmlPart?.body || void 0;
49
+ const text = (textPart?.Body || textPart?.body || item.Content?.Body || item.Content?.body || "").toString();
50
+ const subject = getHeaderValue(headers, "Subject") || "";
51
+ const from = getHeaderValue(headers, "From") || "";
52
+ const to = getHeaderValue(headers, "To") || "";
53
+ const cc = getHeaderValue(headers, "Cc") || void 0;
54
+ const attachments = parts.filter((p) => {
55
+ const ct = (p.ContentType || p.contentType || "").toString();
56
+ const filename = p.FileName || p.Filename || p.filename;
57
+ if (filename) return true;
58
+ return ct && !ct.startsWith("text/");
59
+ }).map((p) => ({
60
+ filename: p.FileName || p.Filename || p.filename || "attachment",
61
+ contentType: p.ContentType || p.contentType || "application/octet-stream"
62
+ }));
63
+ const email = {
64
+ timestamp,
65
+ from,
66
+ to,
67
+ cc,
68
+ subject,
69
+ html,
70
+ text,
71
+ attachments: attachments.length ? attachments : void 0
72
+ };
73
+ return email;
74
+ });
75
+ }
76
+ async function receiveMail(emailAddress, options = {}) {
77
+ const deadline = Date.now() + (options.timeout || (options.wait ? 12e4 : 5e3));
78
+ const pollInterval = 1e3;
79
+ const signal = options.signal ?? AbortSignal.timeout(Math.max(0, deadline - Date.now()));
80
+ while (!signal.aborted) {
81
+ try {
82
+ const items = await fetchOnce(emailAddress, signal);
83
+ let emails = mapItemsToEmails(items);
84
+ if (options.after) emails = emails.filter((e) => e.timestamp > options.after);
85
+ if (options.subject) emails = emails.filter((e) => e.subject.includes(options.subject));
86
+ if (options.limit && options.limit > 0) emails = emails.slice(0, options.limit);
87
+ if (emails.length > 0) {
88
+ return emails;
89
+ }
90
+ } catch (e) {
91
+ if (!signal.aborted) throw e;
92
+ }
93
+ if (!options.wait) break;
94
+ await sleep(pollInterval, { signal });
95
+ }
96
+ return [];
97
+ }
98
+ function buildSearchQuery(emailAddress, options) {
99
+ const terms = [`to:${emailAddress}`];
100
+ if (options.subject) {
101
+ const escaped = options.subject.replace(/"/g, '\\"');
102
+ terms.push(`subject:"${escaped}"`);
103
+ }
104
+ if (options.after) {
105
+ const iso = new Date(options.after).toISOString();
106
+ terms.push(`after:${iso}`);
107
+ }
108
+ return terms.join(" ");
109
+ }
110
+ async function search(emailAddress, options, signal) {
111
+ const base = MAILPIT_BASE_URL.replace(/\/$/, "");
112
+ const query = buildSearchQuery(emailAddress, options);
113
+ const limitParam = options.limit && options.limit > 0 ? `&limit=${encodeURIComponent(String(options.limit))}` : "";
114
+ const url = `${base}/api/v1/search?query=${encodeURIComponent(query)}${limitParam}`;
115
+ const res = await fetch(url, { signal });
116
+ if (!res.ok) {
117
+ const text = await res.text();
118
+ throw new Error(`Failed to fetch response from mailpit: ${res.status} ${text}`);
119
+ }
120
+ const body = await res.json();
121
+ return body?.messages ?? [];
122
+ }
123
+ async function fetchFullMessage(id, signal) {
124
+ const base = MAILPIT_BASE_URL.replace(/\/$/, "");
125
+ const url = `${base}/api/v1/message/${encodeURIComponent(id)}`;
126
+ const res = await fetch(url, { signal });
127
+ if (!res.ok) return null;
128
+ try {
129
+ return await res.json();
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ function pickAddress(obj) {
135
+ if (!obj) return "";
136
+ if (typeof obj === "string") return obj;
137
+ const name = obj.Name;
138
+ const addr = obj.Address || "";
139
+ return name ? `${name} <${addr}>` : addr;
140
+ }
141
+ function joinAddresses(list) {
142
+ if (!Array.isArray(list) || list.length === 0) return "";
143
+ return list.map(pickAddress).filter(Boolean).join(", ");
144
+ }
145
+ function mapMessageToEmail(m) {
146
+ const attachments = Array.isArray(m.Attachments) ? m.Attachments.map((a) => ({
147
+ filename: a.FileName,
148
+ contentType: a.ContentType
149
+ })) : void 0;
150
+ return {
151
+ timestamp: Date.parse(m.Created ?? m.Date),
152
+ from: pickAddress(m.From),
153
+ to: joinAddresses(m.To),
154
+ cc: m.Cc && joinAddresses(m.Cc),
155
+ subject: m.Subject,
156
+ html: m.HTML,
157
+ text: m.Text,
158
+ attachments
159
+ };
160
+ }
161
+ async function fetchFullEmails(messages, signal) {
162
+ const ids = messages.map((m) => m.ID).filter(Boolean);
163
+ const details = await Promise.all(ids.map((id) => fetchFullMessage(id, signal)));
164
+ return details.filter(Boolean).map((d) => mapMessageToEmail(d));
165
+ }
166
+ async function receiveMail2(emailAddress, options = {}) {
167
+ const pollInterval = 1e3;
168
+ const timeout = options.timeout || (options.wait ? 6e4 : 5e3);
169
+ const signal = options.signal ?? AbortSignal.timeout(timeout);
170
+ while (!signal.aborted) {
171
+ try {
172
+ const messages = await search(emailAddress, options, signal);
173
+ const emails = options.full ? await fetchFullEmails(messages, signal) : messages.map((m) => mapMessageToEmail(m));
174
+ if (emails.length > 0) {
175
+ return emails;
176
+ }
177
+ } catch (e) {
178
+ if (!signal.aborted) throw e;
179
+ }
180
+ if (!options.wait) break;
181
+ await sleep(pollInterval, { signal });
182
+ }
183
+ return [];
184
+ }
185
+ async function receiveMail3(emailAddress, options = {}) {
186
+ if (!TESTMAIL_API_KEY) throw new Error("TESTMAIL_API_KEY environment var not set");
187
+ const match = emailAddress.match(/^(?<namespace>[^.@]+)\.(?<tag>[^@]+)@/);
188
+ if (!match) throw new Error("Email address is not a valid testmail address");
189
+ const namespace = match.groups.namespace;
190
+ const tag = match.groups.tag;
191
+ const signal = options.signal ?? AbortSignal.timeout(options.timeout || (options.wait ? 12e4 : 5e3));
192
+ const client = new GraphQLClient(TESTMAIL_GRAPHQL_URL, {
193
+ headers: { apikey: TESTMAIL_API_KEY },
194
+ fetch: (input, init = {}) => {
195
+ return fetch(input, { ...init, signal });
196
+ }
197
+ });
198
+ const fields = ["timestamp", "from", "to", "cc", "subject"];
199
+ if (options.full) fields.push("html", "text", "attachments { filename contentType }");
200
+ const query = `
201
+ query Inbox($namespace: String!, $tag: String!, $timestampFrom: Long, $subject: String, $limit: Int) {
202
+ inbox(
203
+ namespace: $namespace
204
+ tag: $tag
205
+ ${options.wait ? "livequery: true" : ""}
206
+ ${options.after ? "timestamp_from: $timestampFrom" : ""}
207
+ ${options.subject ? `advanced_filters: [{ field: subject, match: exact, action: include, value: $subject }]` : ""}
208
+ ${options.limit ? "limit: $limit" : ""}
209
+ advanced_sorts: [{ field: timestamp, order: desc }]
210
+ ) {
211
+ emails {
212
+ ${fields.join("\n ")}
213
+ }
214
+ }
215
+ }
216
+ `;
217
+ const variables = clean({
218
+ namespace,
219
+ tag,
220
+ timestampFrom: options.after,
221
+ subject: options.subject,
222
+ limit: options.limit
223
+ });
224
+ try {
225
+ const data = await client.request(query, variables);
226
+ return data?.inbox?.emails || [];
227
+ } catch (err) {
228
+ const message = err?.response?.errors?.[0]?.message || err?.message || "Unknown error";
229
+ throw new Error(`Failed to fetch response from testmail: ${message}`);
230
+ }
231
+ }
232
+
233
+ // src/receive.ts
234
+ async function receiveMail4(emailAddress, options = {}) {
235
+ switch (MAILBOX_SERVICE) {
236
+ case "testmail":
237
+ return await receiveMail3(emailAddress, options);
238
+ case "mailhog":
239
+ return await receiveMail(emailAddress, options);
240
+ case "mailpit":
241
+ return await receiveMail2(emailAddress, options);
242
+ default:
243
+ throw new Error(`Unsupported mailbox service ${MAILBOX_SERVICE}`);
244
+ }
245
+ }
246
+
247
+ // src/serialize.ts
248
+ function toEml(email) {
249
+ const lines = [];
250
+ const crlf = (s) => s.replace(/\n/g, "\r\n");
251
+ const date = new Date(email.timestamp);
252
+ lines.push(`Date: ${date.toUTCString()}`);
253
+ lines.push(`From: ${email.from}`);
254
+ lines.push(`To: ${email.to}`);
255
+ if (email.cc) lines.push(`Cc: ${email.cc}`);
256
+ lines.push(`Subject: ${email.subject}`);
257
+ lines.push("MIME-Version: 1.0");
258
+ const hasText = typeof email.text === "string" && email.text.length > 0;
259
+ const hasHtml = typeof email.html === "string" && email.html.length > 0;
260
+ const hasAttachments = Array.isArray(email.attachments) && email.attachments.length > 0;
261
+ const boundary = `===============lr_${Math.random().toString(36).slice(2)}_${Date.now()}==`;
262
+ const altBoundary = `===============lr_alt_${Math.random().toString(36).slice(2)}_${Date.now()}==`;
263
+ function pushTextPart() {
264
+ lines.push(`Content-Type: text/plain; charset=utf-8`);
265
+ lines.push("Content-Transfer-Encoding: 7bit");
266
+ lines.push("");
267
+ lines.push(crlf(hasText ? email.text : ""));
268
+ }
269
+ function pushHtmlPart() {
270
+ lines.push(`Content-Type: text/html; charset=utf-8`);
271
+ lines.push("Content-Transfer-Encoding: 7bit");
272
+ lines.push("");
273
+ lines.push(crlf(hasHtml ? email.html : ""));
274
+ }
275
+ if (hasAttachments) {
276
+ lines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
277
+ lines.push("");
278
+ lines.push(`--${boundary}`);
279
+ if (hasText && hasHtml) {
280
+ lines.push(`Content-Type: multipart/alternative; boundary="${altBoundary}"`);
281
+ lines.push("");
282
+ lines.push(`--${altBoundary}`);
283
+ pushTextPart();
284
+ lines.push(`--${altBoundary}`);
285
+ pushHtmlPart();
286
+ lines.push(`--${altBoundary}--`);
287
+ } else if (hasText) {
288
+ pushTextPart();
289
+ } else {
290
+ pushHtmlPart();
291
+ }
292
+ for (const a of email.attachments || []) {
293
+ lines.push(`--${boundary}`);
294
+ const disp = `attachment; filename="${a.filename}"`;
295
+ lines.push(`Content-Type: ${a.contentType}; name="${a.filename}"`);
296
+ lines.push(`Content-Disposition: ${disp}`);
297
+ lines.push("Content-Transfer-Encoding: base64");
298
+ lines.push("");
299
+ lines.push("");
300
+ }
301
+ lines.push(`--${boundary}--`);
302
+ } else if (hasText && hasHtml) {
303
+ lines.push(`Content-Type: multipart/alternative; boundary="${altBoundary}"`);
304
+ lines.push("");
305
+ lines.push(`--${altBoundary}`);
306
+ pushTextPart();
307
+ lines.push(`--${altBoundary}`);
308
+ pushHtmlPart();
309
+ lines.push(`--${altBoundary}--`);
310
+ } else if (hasText) {
311
+ pushTextPart();
312
+ } else {
313
+ pushHtmlPart();
314
+ }
315
+ return lines.join("\r\n");
316
+ }
317
+ function fromEml(contents) {
318
+ const raw = contents.replace(/\r\n/g, "\n");
319
+ const [rawHeader, ...rest] = raw.split(/\n\n/);
320
+ const headerLines = rawHeader.split("\n");
321
+ const unfolded = [];
322
+ for (const line of headerLines) {
323
+ if (/^[ \t]/.test(line) && unfolded.length > 0) {
324
+ unfolded[unfolded.length - 1] += line.replace(/^\s+/, " ");
325
+ } else {
326
+ unfolded.push(line);
327
+ }
328
+ }
329
+ const headers = /* @__PURE__ */ new Map();
330
+ for (const l of unfolded) {
331
+ const idx = l.indexOf(":");
332
+ if (idx === -1) continue;
333
+ const key = l.slice(0, idx).trim().toLowerCase();
334
+ const val = l.slice(idx + 1).trim();
335
+ headers.set(key, val);
336
+ }
337
+ const body = rest.join("\n\n");
338
+ const email = {
339
+ timestamp: Date.parse(headers.get("date") || (/* @__PURE__ */ new Date()).toUTCString()),
340
+ from: headers.get("from") || "",
341
+ to: headers.get("to") || "",
342
+ cc: headers.get("cc") || void 0,
343
+ subject: headers.get("subject") || ""
344
+ };
345
+ const ct = (headers.get("content-type") || "").toLowerCase();
346
+ function parseParts(contentType, data) {
347
+ const result = { attachments: [] };
348
+ if (contentType.startsWith("multipart/")) {
349
+ const m = contentType.match(/boundary="?([^";]+)"?/);
350
+ const boundary = m ? m[1] : "";
351
+ if (!boundary) return result;
352
+ const lines = data.split("\n");
353
+ let i = 0;
354
+ while (i < lines.length) {
355
+ const line = lines[i];
356
+ if (line === `--${boundary}`) {
357
+ i++;
358
+ const pHeaders = /* @__PURE__ */ new Map();
359
+ const headAccum = [];
360
+ for (; i < lines.length; i++) {
361
+ const l = lines[i];
362
+ if (l === "") break;
363
+ headAccum.push(l);
364
+ }
365
+ if (i < lines.length && lines[i] === "") i++;
366
+ const unfolded2 = [];
367
+ for (const h of headAccum) {
368
+ if ((h.startsWith(" ") || h.startsWith(" ")) && unfolded2.length) {
369
+ unfolded2[unfolded2.length - 1] += h.replace(/^\s+/, " ");
370
+ } else {
371
+ unfolded2.push(h);
372
+ }
373
+ }
374
+ for (const h of unfolded2) {
375
+ const idx = h.indexOf(":");
376
+ if (idx !== -1) pHeaders.set(h.slice(0, idx).trim().toLowerCase(), h.slice(idx + 1).trim());
377
+ }
378
+ const bodyLines = [];
379
+ for (; i < lines.length; i++) {
380
+ const l = lines[i];
381
+ if (l === `--${boundary}` || l === `--${boundary}--`) break;
382
+ bodyLines.push(l);
383
+ }
384
+ const pBody = bodyLines.join("\n");
385
+ const pct = (pHeaders.get("content-type") || "").toLowerCase();
386
+ const disp = (pHeaders.get("content-disposition") || "").toLowerCase();
387
+ if (pct.startsWith("multipart/")) {
388
+ const nested = parseParts(pct, pBody);
389
+ if (nested.text && !result.text) result.text = nested.text;
390
+ if (nested.html && !result.html) result.html = nested.html;
391
+ if (nested.attachments && nested.attachments.length) result.attachments.push(...nested.attachments);
392
+ } else if (pct.startsWith("text/plain")) {
393
+ result.text = pBody;
394
+ } else if (pct.startsWith("text/html")) {
395
+ result.html = pBody;
396
+ } else if (disp.startsWith("attachment") || disp.includes("filename=")) {
397
+ let filename = "";
398
+ const fnm = disp.match(/filename="?([^";]+)"?/);
399
+ if (fnm) filename = fnm[1];
400
+ if (!filename) {
401
+ const nm = pct.match(/name="?([^";]+)"?/);
402
+ if (nm) filename = nm[1];
403
+ }
404
+ const contentTypeHeader = pHeaders.get("content-type") || "application/octet-stream";
405
+ result.attachments.push({ filename, contentType: contentTypeHeader.split(";")[0] });
406
+ }
407
+ if (i < lines.length && lines[i] === `--${boundary}--`) {
408
+ break;
409
+ }
410
+ } else if (line === `--${boundary}--`) {
411
+ break;
412
+ } else {
413
+ i++;
414
+ }
415
+ }
416
+ const segs = `
417
+ ${data}`.split(`
418
+ --${boundary}`);
419
+ const have = new Set(result.attachments.map((a) => `${a.filename}|${a.contentType}`));
420
+ for (let seg of segs) {
421
+ seg = seg.replace(/^\n/, "");
422
+ if (!seg || seg.startsWith("--")) continue;
423
+ const [head] = seg.split(/\n\n/);
424
+ const disp = (head.match(/(^|\n)content-disposition:\s*([^\n]+)/i)?.[2] || "").toLowerCase();
425
+ if (!disp.includes("attachment")) continue;
426
+ let filename = "";
427
+ const fnm = disp.match(/filename="?([^";]+)"?/);
428
+ if (fnm) filename = fnm[1];
429
+ const ct2 = (head.match(/(^|\n)content-type:\s*([^\n]+)/i)?.[2] || "application/octet-stream").split(";")[0];
430
+ const key = `${filename}|${ct2}`;
431
+ if (!have.has(key)) {
432
+ have.add(key);
433
+ result.attachments.push({ filename, contentType: ct2 });
434
+ }
435
+ }
436
+ if (result.attachments && result.attachments.length > 1) {
437
+ const positions = /* @__PURE__ */ new Map();
438
+ for (const a of result.attachments) {
439
+ const key = `${a.filename}|${a.contentType}`;
440
+ if (!positions.has(key)) {
441
+ const re = new RegExp(`filename="?${a.filename.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"?`, "i");
442
+ const idx = data.search(re);
443
+ positions.set(key, idx === -1 ? Number.MAX_SAFE_INTEGER : idx);
444
+ }
445
+ }
446
+ result.attachments.sort((a, b) => {
447
+ const ka = `${a.filename}|${a.contentType}`;
448
+ const kb = `${b.filename}|${b.contentType}`;
449
+ return (positions.get(ka) ?? 0) - (positions.get(kb) ?? 0);
450
+ });
451
+ }
452
+ return result;
453
+ }
454
+ if (contentType.startsWith("text/plain")) {
455
+ result.text = data;
456
+ } else if (contentType.startsWith("text/html")) {
457
+ result.html = data;
458
+ }
459
+ return result;
460
+ }
461
+ const parsed = parseParts(ct, body);
462
+ if (parsed.text !== void 0) email.text = parsed.text;
463
+ if (parsed.html !== void 0) email.html = parsed.html;
464
+ if (parsed.attachments && parsed.attachments.length) email.attachments = parsed.attachments;
465
+ return email;
466
+ }
467
+
468
+ export { fromEml, getMailbox, receiveMail4 as receiveMail, toEml };
469
+ //# sourceMappingURL=index.js.map
470
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/mailbox.ts","../src/mailhog/receive.ts","../src/mailpit/receive.ts","../src/testmail/receive.ts","../src/receive.ts","../src/serialize.ts"],"names":["receiveMail","sleep","clean","unfolded","ct"],"mappings":"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","file":"index.js","sourcesContent":["const testValue = (value: string) => process.env.NODE_ENV === 'test' ? value : null;\n\nexport const TESTMAIL_DOMAIN = 'inbox.testmail.app';\nexport const TESTMAIL_API_KEY = process.env.TESTMAIL_API_KEY || testValue('test_key');\nexport const TESTMAIL_NAMESPACE = process.env.TESTMAIL_NAMESPACE || testValue('test_ns');\nexport const TESTMAIL_GRAPHQL_URL = 'https://api.testmail.app/api/graphql';\n\nexport const MAILHOG_BASE_URL = process.env.MAILHOG_BASE_URL || 'http://localhost:8025';\n\nexport const MAILPIT_BASE_URL = process.env.MAILPIT_BASE_URL || 'http://localhost:8025';\n\nexport const MAILBOX_SERVICE = process.env.MAILBOX_SERVICE || 'mailpit';\nexport const MAILBOX_DOMAIN = process.env.MAILBOX_DOMAIN\n || (MAILBOX_SERVICE === 'testmail' ? TESTMAIL_DOMAIN : 'example.com');\n","import type { UUID } from '@letsrunit/utils';\nimport { clean, uuidToTag } from '@letsrunit/utils';\nimport { MAILBOX_DOMAIN, TESTMAIL_DOMAIN, TESTMAIL_NAMESPACE } from './constants';\n\nexport function getMailbox(seed: UUID, name?: string, domain?: string) {\n domain ??= MAILBOX_DOMAIN;\n\n const ns = domain === TESTMAIL_DOMAIN ? TESTMAIL_NAMESPACE : null;\n const local = clean([ns, uuidToTag(seed), name]).join('.');\n\n return `${local}@${domain}`;\n}\n","import { sleep } from '@letsrunit/utils';\nimport { MAILHOG_BASE_URL } from '../constants';\nimport type { Email, ReceiveOptions } from '../types';\n\nasync function fetchOnce(emailAddress: string, signal: AbortSignal): Promise<any[]> {\n const url = `${MAILHOG_BASE_URL.replace(/\\/$/, '')}/api/v2/search?kind=to&query=${encodeURIComponent(emailAddress)}`;\n const res = await fetch(url, { signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Failed to fetch response from mailhog: ${res.status} ${text}`);\n }\n const body = await res.json();\n return body?.items ?? body?.Items ?? [];\n}\n\nfunction getHeaderValue(headers: Record<string, string[] | string> | undefined, name: string): string | undefined {\n if (!headers) return undefined;\n const v = headers[name] ?? headers[name.toLowerCase() as keyof typeof headers];\n if (!v) return undefined;\n return Array.isArray(v) ? v[0] : v;\n}\n\nfunction mapItemsToEmails(items: any[]): Email[] {\n return items.map((item) => {\n // timestamp\n const created = item.Created || item.created || item.Time;\n const timestamp = typeof created === 'number' ? created : Date.parse(created);\n\n // headers\n const headers: Record<string, string[] | string> | undefined = item.Content?.Headers || item.Content?.headers;\n\n // bodies from parts\n const parts: any[] = item.MIME?.Parts || item.MIME?.parts || [];\n const htmlPart = parts.find((p) => (p.ContentType || p.contentType || '').startsWith('text/html'));\n const textPart = parts.find((p) => (p.ContentType || p.contentType || '').startsWith('text/plain'));\n\n const html = htmlPart?.Body || htmlPart?.body || undefined;\n const text = (textPart?.Body || textPart?.body || item.Content?.Body || item.Content?.body || '').toString();\n\n // basic fields\n const subject = getHeaderValue(headers, 'Subject') || '';\n const from = getHeaderValue(headers, 'From') || '';\n const to = getHeaderValue(headers, 'To') || '';\n const cc = getHeaderValue(headers, 'Cc') || undefined;\n\n // attachments\n const attachments = parts\n .filter((p) => {\n const ct = (p.ContentType || p.contentType || '').toString();\n const filename = p.FileName || p.Filename || p.filename;\n if (filename) return true;\n return ct && !ct.startsWith('text/');\n })\n .map((p) => ({\n filename: p.FileName || p.Filename || p.filename || 'attachment',\n contentType: p.ContentType || p.contentType || 'application/octet-stream',\n }));\n\n const email: Email = {\n timestamp,\n from,\n to,\n cc,\n subject,\n html,\n text,\n attachments: attachments.length ? attachments : undefined,\n };\n\n return email;\n });\n}\n\nexport async function receiveMail(emailAddress: string, options: ReceiveOptions = {}): Promise<Email[]> {\n const deadline = Date.now() + (options.timeout || (options.wait ? 120_000 : 5_000));\n const pollInterval = 1_000;\n const signal: AbortSignal = options.signal ?? AbortSignal.timeout(Math.max(0, deadline - Date.now()));\n\n while (!signal.aborted) {\n try {\n const items = await fetchOnce(emailAddress, signal);\n let emails = mapItemsToEmails(items);\n\n if (options.after) emails = emails.filter((e) => e.timestamp > options.after!);\n if (options.subject) emails = emails.filter((e) => e.subject.includes(options.subject!));\n if (options.limit && options.limit > 0) emails = emails.slice(0, options.limit);\n\n if (emails.length > 0) {\n return emails;\n }\n } catch (e) {\n // bubble up fetch errors except when aborted due to signal; then just end loop\n if (!signal.aborted) throw e;\n }\n\n if (!options.wait) break;\n await sleep(pollInterval, { signal });\n }\n\n return [];\n}\n","import { sleep } from '@letsrunit/utils';\nimport { MAILPIT_BASE_URL } from '../constants';\nimport type { Email, ReceiveOptions } from '../types';\n\nfunction buildSearchQuery(emailAddress: string, options: ReceiveOptions): string {\n const terms: string[] = [`to:${emailAddress}`];\n if (options.subject) {\n const escaped = options.subject.replace(/\"/g, '\\\\\"');\n terms.push(`subject:\"${escaped}\"`);\n }\n if (options.after) {\n const iso = new Date(options.after).toISOString();\n terms.push(`after:${iso}`);\n }\n return terms.join(' ');\n}\n\nasync function search(emailAddress: string, options: ReceiveOptions, signal: AbortSignal): Promise<any[]> {\n const base = MAILPIT_BASE_URL.replace(/\\/$/, '');\n const query = buildSearchQuery(emailAddress, options);\n const limitParam = options.limit && options.limit > 0 ? `&limit=${encodeURIComponent(String(options.limit))}` : '';\n const url = `${base}/api/v1/search?query=${encodeURIComponent(query)}${limitParam}`;\n const res = await fetch(url, { signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Failed to fetch response from mailpit: ${res.status} ${text}`);\n }\n const body = await res.json();\n return body?.messages ?? [];\n}\n\nasync function fetchFullMessage(\n id: string,\n signal: AbortSignal,\n): Promise<{\n Html?: string;\n Text?: string;\n Attachments?: any[];\n Created?: string | number;\n Subject?: string;\n From?: any;\n To?: any[];\n Cc?: any[];\n} | null> {\n const base = MAILPIT_BASE_URL.replace(/\\/$/, '');\n const url = `${base}/api/v1/message/${encodeURIComponent(id)}`;\n const res = await fetch(url, { signal });\n if (!res.ok) return null;\n try {\n return await res.json();\n } catch {\n return null;\n }\n}\n\nfunction pickAddress(obj: any): string {\n if (!obj) return '';\n if (typeof obj === 'string') return obj;\n const name = obj.Name;\n const addr = obj.Address || '';\n return name ? `${name} <${addr}>` : addr;\n}\n\nfunction joinAddresses(list?: any[]): string {\n if (!Array.isArray(list) || list.length === 0) return '';\n return list.map(pickAddress).filter(Boolean).join(', ');\n}\n\nfunction mapMessageToEmail(m: any): Email {\n const attachments = Array.isArray(m.Attachments)\n ? m.Attachments.map((a: any) => ({\n filename: a.FileName,\n contentType: a.ContentType,\n }))\n : undefined;\n\n return {\n timestamp: Date.parse(m.Created ?? m.Date),\n from: pickAddress(m.From),\n to: joinAddresses(m.To),\n cc: m.Cc && joinAddresses(m.Cc),\n subject: m.Subject,\n html: m.HTML,\n text: m.Text,\n attachments,\n };\n}\n\nasync function fetchFullEmails(messages: any[], signal: AbortSignal): Promise<Email[]> {\n const ids: string[] = messages.map((m: any) => m.ID).filter(Boolean);\n const details = await Promise.all(ids.map((id) => fetchFullMessage(id, signal)));\n return details.filter(Boolean).map((d) => mapMessageToEmail(d));\n}\n\nexport async function receiveMail(emailAddress: string, options: ReceiveOptions = {}): Promise<Email[]> {\n const pollInterval = 1_000;\n const timeout = options.timeout || (options.wait ? 60_000 : 5_000);\n const signal: AbortSignal = options.signal ?? AbortSignal.timeout(timeout);\n\n while (!signal.aborted) {\n try {\n const messages = await search(emailAddress, options, signal);\n const emails = options.full\n ? await fetchFullEmails(messages, signal)\n : messages.map((m) => mapMessageToEmail(m));\n\n if (emails.length > 0) {\n return emails;\n }\n } catch (e) {\n if (!signal.aborted) throw e;\n }\n\n if (!options.wait) break;\n await sleep(pollInterval, { signal });\n }\n\n return [];\n}\n","import { clean } from '@letsrunit/utils';\nimport { GraphQLClient } from 'graphql-request';\nimport { TESTMAIL_API_KEY, TESTMAIL_GRAPHQL_URL } from '../constants';\nimport type { Email, ReceiveOptions } from '../types';\n\nexport async function receiveMail(emailAddress: string, options: ReceiveOptions = {}): Promise<Email[]> {\n if (!TESTMAIL_API_KEY) throw new Error('TESTMAIL_API_KEY environment var not set');\n\n const match = emailAddress.match(/^(?<namespace>[^.@]+)\\.(?<tag>[^@]+)@/);\n if (!match) throw new Error('Email address is not a valid testmail address');\n\n const namespace = match.groups!.namespace;\n const tag = match.groups!.tag;\n\n const signal = options.signal ?? AbortSignal.timeout(options.timeout || (options.wait ? 120_000 : 5000));\n\n const client = new GraphQLClient(TESTMAIL_GRAPHQL_URL, {\n headers: { apikey: TESTMAIL_API_KEY },\n fetch: (input, init = {}) => {\n return fetch(input as RequestInfo, { ...init, signal });\n },\n });\n\n const fields = ['timestamp', 'from', 'to', 'cc', 'subject'];\n if (options.full) fields.push('html', 'text', 'attachments { filename contentType }');\n\n const query = `\n query Inbox($namespace: String!, $tag: String!, $timestampFrom: Long, $subject: String, $limit: Int) {\n inbox(\n namespace: $namespace\n tag: $tag\n ${options.wait ? 'livequery: true' : ''}\n ${options.after ? 'timestamp_from: $timestampFrom' : ''}\n ${options.subject ? `advanced_filters: [{ field: subject, match: exact, action: include, value: $subject }]` : ''}\n ${options.limit ? 'limit: $limit' : ''}\n advanced_sorts: [{ field: timestamp, order: desc }]\n ) {\n emails {\n ${fields.join('\\n ')}\n }\n }\n }\n `;\n\n const variables: Record<string, any> = clean({\n namespace,\n tag,\n timestampFrom: options.after,\n subject: options.subject,\n limit: options.limit,\n });\n\n try {\n const data: any = await client.request(query, variables);\n return data?.inbox?.emails || [];\n } catch (err: any) {\n const message = err?.response?.errors?.[0]?.message || err?.message || 'Unknown error';\n throw new Error(`Failed to fetch response from testmail: ${message}`);\n }\n}\n","import { MAILBOX_SERVICE } from './constants';\nimport { receiveMail as mailhogReceive } from './mailhog/receive';\nimport { receiveMail as mailpitReceive } from './mailpit/receive';\nimport { receiveMail as testmailReceive } from './testmail/receive';\nimport type { Email, ReceiveOptions } from './types';\n\nexport async function receiveMail(emailAddress: string, options: ReceiveOptions = {}): Promise<Email[]> {\n switch (MAILBOX_SERVICE) {\n case 'testmail':\n return await testmailReceive(emailAddress, options);\n case 'mailhog':\n return await mailhogReceive(emailAddress, options);\n case 'mailpit':\n return await mailpitReceive(emailAddress, options);\n default:\n throw new Error(`Unsupported mailbox service ${MAILBOX_SERVICE}`);\n }\n}\n","import type { Email } from './types';\n\nexport function toEml(email: Email): string {\n const lines: string[] = [];\n const crlf = (s: string) => s.replace(/\\n/g, '\\r\\n');\n\n const date = new Date(email.timestamp);\n // Use RFC 2822 format via toUTCString\n lines.push(`Date: ${date.toUTCString()}`);\n lines.push(`From: ${email.from}`);\n lines.push(`To: ${email.to}`);\n if (email.cc) lines.push(`Cc: ${email.cc}`);\n lines.push(`Subject: ${email.subject}`);\n lines.push('MIME-Version: 1.0');\n\n const hasText = typeof email.text === 'string' && email.text.length > 0;\n const hasHtml = typeof email.html === 'string' && email.html.length > 0;\n const hasAttachments = Array.isArray(email.attachments) && email.attachments.length > 0;\n\n const boundary = `===============lr_${Math.random().toString(36).slice(2)}_${Date.now()}==`;\n const altBoundary = `===============lr_alt_${Math.random().toString(36).slice(2)}_${Date.now()}==`;\n\n function pushTextPart() {\n lines.push(`Content-Type: text/plain; charset=utf-8`);\n lines.push('Content-Transfer-Encoding: 7bit');\n lines.push('');\n lines.push(crlf(hasText ? email.text! : ''));\n }\n\n function pushHtmlPart() {\n lines.push(`Content-Type: text/html; charset=utf-8`);\n lines.push('Content-Transfer-Encoding: 7bit');\n lines.push('');\n lines.push(crlf(hasHtml ? email.html! : ''));\n }\n\n // Build structure\n if (hasAttachments) {\n // multipart/mixed enclosing either single/alternative plus attachments\n lines.push(`Content-Type: multipart/mixed; boundary=\"${boundary}\"`);\n lines.push('');\n lines.push(`--${boundary}`);\n if (hasText && hasHtml) {\n // nested multipart/alternative\n lines.push(`Content-Type: multipart/alternative; boundary=\"${altBoundary}\"`);\n lines.push('');\n // text part\n lines.push(`--${altBoundary}`);\n pushTextPart();\n // html part\n lines.push(`--${altBoundary}`);\n pushHtmlPart();\n // end alternative\n lines.push(`--${altBoundary}--`);\n } else if (hasText) {\n pushTextPart();\n } else {\n pushHtmlPart();\n }\n\n // attachments (metadata only; empty bodies)\n for (const a of email.attachments || []) {\n lines.push(`--${boundary}`);\n const disp = `attachment; filename=\"${a.filename}\"`;\n lines.push(`Content-Type: ${a.contentType}; name=\"${a.filename}\"`);\n lines.push(`Content-Disposition: ${disp}`);\n lines.push('Content-Transfer-Encoding: base64');\n lines.push('');\n // No content stored in type, emit empty body to keep structure valid\n lines.push('');\n }\n lines.push(`--${boundary}--`);\n } else if (hasText && hasHtml) {\n // multipart/alternative\n lines.push(`Content-Type: multipart/alternative; boundary=\"${altBoundary}\"`);\n lines.push('');\n lines.push(`--${altBoundary}`);\n pushTextPart();\n lines.push(`--${altBoundary}`);\n pushHtmlPart();\n lines.push(`--${altBoundary}--`);\n } else if (hasText) {\n pushTextPart();\n } else {\n pushHtmlPart();\n }\n\n // Ensure CRLF endings\n return lines.join('\\r\\n');\n}\n\nexport function fromEml(contents: string): Email {\n // Normalize line endings to \\n for parsing\n const raw = contents.replace(/\\r\\n/g, '\\n');\n const [rawHeader, ...rest] = raw.split(/\\n\\n/);\n const headerLines = rawHeader.split('\\n');\n // Handle folded headers (lines starting with space or tab)\n const unfolded: string[] = [];\n for (const line of headerLines) {\n if (/^[ \\t]/.test(line) && unfolded.length > 0) {\n unfolded[unfolded.length - 1] += line.replace(/^\\s+/, ' ');\n } else {\n unfolded.push(line);\n }\n }\n const headers = new Map<string, string>();\n for (const l of unfolded) {\n const idx = l.indexOf(':');\n if (idx === -1) continue;\n const key = l.slice(0, idx).trim().toLowerCase();\n const val = l.slice(idx + 1).trim();\n headers.set(key, val);\n }\n const body = rest.join('\\n\\n');\n\n const email: Email = {\n timestamp: Date.parse(headers.get('date') || new Date().toUTCString()),\n from: headers.get('from') || '',\n to: headers.get('to') || '',\n cc: headers.get('cc') || undefined,\n subject: headers.get('subject') || '',\n } as Email;\n\n // Parse body depending on content-type\n const ct = (headers.get('content-type') || '').toLowerCase();\n\n function parseParts(contentType: string, data: string): { text?: string; html?: string; attachments?: { filename: string; contentType: string }[] } {\n const result: { text?: string; html?: string; attachments?: { filename: string; contentType: string }[] } = { attachments: [] };\n if (contentType.startsWith('multipart/')) {\n const m = contentType.match(/boundary=\"?([^\";]+)\"?/);\n const boundary = m ? m[1] : '';\n if (!boundary) return result;\n // Line-oriented multipart parsing\n const lines = data.split('\\n');\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n if (line === `--${boundary}`) {\n // Parse headers\n i++;\n const pHeaders = new Map<string, string>();\n const headAccum: string[] = [];\n for (; i < lines.length; i++) {\n const l = lines[i];\n if (l === '') break;\n headAccum.push(l);\n }\n // skip empty line\n if (i < lines.length && lines[i] === '') i++;\n // unfold and map headers\n const unfolded: string[] = [];\n for (const h of headAccum) {\n if ((h.startsWith(' ') || h.startsWith('\\t')) && unfolded.length) {\n unfolded[unfolded.length - 1] += h.replace(/^\\s+/, ' ');\n } else {\n unfolded.push(h);\n }\n }\n for (const h of unfolded) {\n const idx = h.indexOf(':');\n if (idx !== -1) pHeaders.set(h.slice(0, idx).trim().toLowerCase(), h.slice(idx + 1).trim());\n }\n // Collect body until next boundary marker\n const bodyLines: string[] = [];\n for (; i < lines.length; i++) {\n const l = lines[i];\n if (l === `--${boundary}` || l === `--${boundary}--`) break;\n bodyLines.push(l);\n }\n const pBody = bodyLines.join('\\n');\n\n const pct = (pHeaders.get('content-type') || '').toLowerCase();\n const disp = (pHeaders.get('content-disposition') || '').toLowerCase();\n if (pct.startsWith('multipart/')) {\n const nested = parseParts(pct, pBody);\n if (nested.text && !result.text) result.text = nested.text;\n if (nested.html && !result.html) result.html = nested.html;\n if (nested.attachments && nested.attachments.length) result.attachments!.push(...nested.attachments);\n } else if (pct.startsWith('text/plain')) {\n result.text = pBody;\n } else if (pct.startsWith('text/html')) {\n result.html = pBody;\n } else if (disp.startsWith('attachment') || disp.includes('filename=')) {\n let filename = '';\n const fnm = disp.match(/filename=\"?([^\";]+)\"?/);\n if (fnm) filename = fnm[1];\n if (!filename) {\n const nm = pct.match(/name=\"?([^\";]+)\"?/);\n if (nm) filename = nm[1];\n }\n const contentTypeHeader = pHeaders.get('content-type') || 'application/octet-stream';\n result.attachments!.push({ filename, contentType: contentTypeHeader.split(';')[0] });\n }\n\n // If current line is closing boundary, advance past it and stop\n if (i < lines.length && lines[i] === `--${boundary}--`) {\n break;\n }\n // continue to next line (which is next boundary or end)\n } else if (line === `--${boundary}--`) {\n break;\n } else {\n i++;\n }\n }\n // Fallback pass: ensure we didn't miss any attachment headers\n const segs = (`\\n${data}`).split(`\\n--${boundary}`);\n const have = new Set(result.attachments!.map((a) => `${a.filename}|${a.contentType}`));\n for (let seg of segs) {\n seg = seg.replace(/^\\n/, '');\n if (!seg || seg.startsWith('--')) continue;\n const [head] = seg.split(/\\n\\n/);\n const disp = (head.match(/(^|\\n)content-disposition:\\s*([^\\n]+)/i)?.[2] || '').toLowerCase();\n if (!disp.includes('attachment')) continue;\n let filename = '';\n const fnm = disp.match(/filename=\"?([^\";]+)\"?/);\n if (fnm) filename = fnm[1];\n const ct = (head.match(/(^|\\n)content-type:\\s*([^\\n]+)/i)?.[2] || 'application/octet-stream').split(';')[0];\n const key = `${filename}|${ct}`;\n if (!have.has(key)) {\n have.add(key);\n result.attachments!.push({ filename, contentType: ct });\n }\n }\n // Order attachments by first appearance in the original data\n if (result.attachments && result.attachments.length > 1) {\n const positions = new Map<string, number>();\n for (const a of result.attachments) {\n const key = `${a.filename}|${a.contentType}`;\n if (!positions.has(key)) {\n const re = new RegExp(`filename=\\\"?${a.filename.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\"?`, 'i');\n const idx = data.search(re);\n positions.set(key, idx === -1 ? Number.MAX_SAFE_INTEGER : idx);\n }\n }\n result.attachments.sort((a, b) => {\n const ka = `${a.filename}|${a.contentType}`;\n const kb = `${b.filename}|${b.contentType}`;\n return (positions.get(ka) ?? 0) - (positions.get(kb) ?? 0);\n });\n }\n return result;\n }\n // single part\n if (contentType.startsWith('text/plain')) {\n result.text = data;\n } else if (contentType.startsWith('text/html')) {\n result.html = data;\n }\n return result;\n }\n\n const parsed = parseParts(ct, body);\n if (parsed.text !== undefined) email.text = parsed.text;\n if (parsed.html !== undefined) email.html = parsed.html;\n if (parsed.attachments && parsed.attachments.length) email.attachments = parsed.attachments;\n\n return email;\n}\n"]}