@softure-ai/mailing 0.2.2 → 0.2.4
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/CHANGELOG.md +45 -0
- package/README.md +57 -6
- package/dist/attachments.d.ts +32 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +68 -0
- package/dist/attachments.js.map +1 -0
- package/dist/cli/command.js +8 -1
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +10 -20
- package/dist/cli/run.js.map +1 -1
- package/dist/contract.d.ts +31 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs/delivery-job.d.ts +8 -3
- package/dist/jobs/delivery-job.d.ts.map +1 -1
- package/dist/jobs/delivery-job.js +18 -2
- package/dist/jobs/delivery-job.js.map +1 -1
- package/dist/options.d.ts +5 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +18 -0
- package/dist/options.js.map +1 -1
- package/dist/providers/resend.d.ts.map +1 -1
- package/dist/providers/resend.js +10 -0
- package/dist/providers/resend.js.map +1 -1
- package/dist/server/deliveries.js +1 -1
- package/dist/server/deliveries.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/operator-output.d.ts +6 -0
- package/dist/server/operator-output.d.ts.map +1 -1
- package/dist/server/operator-output.js +23 -0
- package/dist/server/operator-output.js.map +1 -1
- package/dist/server/send-mail.d.ts +10 -3
- package/dist/server/send-mail.d.ts.map +1 -1
- package/dist/server/send-mail.js +57 -13
- package/dist/server/send-mail.js.map +1 -1
- package/dist/server/validate-mail.d.ts +8 -3
- package/dist/server/validate-mail.d.ts.map +1 -1
- package/dist/server/validate-mail.js +52 -12
- package/dist/server/validate-mail.js.map +1 -1
- package/dist/testing/fake-provider.d.ts.map +1 -1
- package/dist/testing/fake-provider.js +18 -3
- package/dist/testing/fake-provider.js.map +1 -1
- package/dist/testing/outbox.d.ts.map +1 -1
- package/dist/testing/outbox.js +10 -1
- package/dist/testing/outbox.js.map +1 -1
- package/module.json +1 -1
- package/package.json +1 -1
- package/src/attachments.ts +90 -0
- package/src/cli/command.ts +10 -2
- package/src/cli/run.ts +10 -20
- package/src/contract.ts +33 -0
- package/src/index.ts +12 -1
- package/src/jobs/delivery-job.ts +24 -4
- package/src/options.ts +19 -0
- package/src/providers/resend.ts +10 -0
- package/src/server/deliveries.ts +1 -1
- package/src/server/index.ts +1 -1
- package/src/server/operator-output.ts +24 -0
- package/src/server/send-mail.ts +61 -14
- package/src/server/validate-mail.ts +60 -13
- package/src/testing/fake-provider.ts +18 -3
- package/src/testing/outbox.ts +11 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A provider that sends nothing: it captures mail in memory (`provider.sent`) and, with an outbox
|
|
2
2
|
// file, appends each mail as one JSON line for a test in another process (an e2e against
|
|
3
|
-
// `next start`). It honours idempotency keys like a real provider: a repeated key answers the
|
|
3
|
+
// `next start`), attachment bytes as base64. It honours idempotency keys like a real provider: a repeated key answers the
|
|
4
4
|
// first id and captures nothing new.
|
|
5
5
|
import { appendFile } from "node:fs/promises";
|
|
6
6
|
import type { MailProvider, ProviderMessage, ProviderOutcome } from "../contract.js";
|
|
@@ -34,6 +34,7 @@ export function fakeMailProvider(options: FakeMailProviderOptions = {}): FakeMai
|
|
|
34
34
|
|
|
35
35
|
return {
|
|
36
36
|
name: "fake",
|
|
37
|
+
supportsAttachments: true,
|
|
37
38
|
get sent() {
|
|
38
39
|
return [...sent];
|
|
39
40
|
},
|
|
@@ -54,9 +55,14 @@ export function fakeMailProvider(options: FakeMailProviderOptions = {}): FakeMai
|
|
|
54
55
|
if (knownId !== undefined) return { status: "sent", id: knownId };
|
|
55
56
|
|
|
56
57
|
counter += 1;
|
|
57
|
-
const captured: CapturedMail = {
|
|
58
|
+
const captured: CapturedMail = {
|
|
59
|
+
...message,
|
|
60
|
+
headers: { ...message.headers },
|
|
61
|
+
...(message.attachments === undefined ? {} : { attachments: message.attachments.map((attachment) => ({ ...attachment, content: attachment.content.slice() })) }),
|
|
62
|
+
id: `fake-${String(counter)}-${crypto.randomUUID()}`,
|
|
63
|
+
};
|
|
58
64
|
if (options.outboxFile !== undefined) {
|
|
59
|
-
await appendFile(options.outboxFile, `${JSON.stringify(captured)}\n`, "utf8");
|
|
65
|
+
await appendFile(options.outboxFile, `${JSON.stringify(toOutboxLine(captured))}\n`, "utf8");
|
|
60
66
|
}
|
|
61
67
|
sent.push(captured);
|
|
62
68
|
if (message.idempotencyKey !== null) idsByKey.set(message.idempotencyKey, captured.id);
|
|
@@ -64,3 +70,12 @@ export function fakeMailProvider(options: FakeMailProviderOptions = {}): FakeMai
|
|
|
64
70
|
},
|
|
65
71
|
};
|
|
66
72
|
}
|
|
73
|
+
|
|
74
|
+
/** A captured mail as one outbox line: attachment bytes as base64 (`readMailOutbox` decodes them). */
|
|
75
|
+
function toOutboxLine(mail: CapturedMail): unknown {
|
|
76
|
+
if (mail.attachments === undefined) return mail;
|
|
77
|
+
return {
|
|
78
|
+
...mail,
|
|
79
|
+
attachments: mail.attachments.map((attachment) => ({ ...attachment, content: Buffer.from(attachment.content).toString("base64") })),
|
|
80
|
+
};
|
|
81
|
+
}
|
package/src/testing/outbox.ts
CHANGED
|
@@ -13,6 +13,10 @@ const capturedMailSchema = z.object({
|
|
|
13
13
|
html: z.string().nullable(),
|
|
14
14
|
headers: z.record(z.string(), z.string()),
|
|
15
15
|
idempotencyKey: z.string().nullable(),
|
|
16
|
+
// Base64 in the file, bytes again here; lines written before attachments existed have none.
|
|
17
|
+
attachments: z
|
|
18
|
+
.array(z.object({ filename: z.string(), contentType: z.string(), content: z.base64().transform((content) => new Uint8Array(Buffer.from(content, "base64"))) }))
|
|
19
|
+
.optional(),
|
|
16
20
|
});
|
|
17
21
|
|
|
18
22
|
/** Every mail in the outbox, oldest first, optionally only those to `to`. A missing file is an empty outbox. */
|
|
@@ -27,6 +31,12 @@ export async function readMailOutbox(file: string, filter: { readonly to?: strin
|
|
|
27
31
|
return text
|
|
28
32
|
.split("\n")
|
|
29
33
|
.filter((line) => line !== "")
|
|
30
|
-
.map((line) => capturedMailSchema.parse(JSON.parse(line)))
|
|
34
|
+
.map((line) => toCapturedMail(capturedMailSchema.parse(JSON.parse(line))))
|
|
31
35
|
.filter((mail) => filter.to === undefined || mail.to === filter.to);
|
|
32
36
|
}
|
|
37
|
+
|
|
38
|
+
/** A parsed line without an `attachments: undefined` key, so a mail without attachments equals what was sent. */
|
|
39
|
+
function toCapturedMail(line: z.output<typeof capturedMailSchema>): CapturedMail {
|
|
40
|
+
const { attachments, ...mail } = line;
|
|
41
|
+
return attachments === undefined ? mail : { ...mail, attachments };
|
|
42
|
+
}
|