@dotpress/mailer 0.0.0-stage → 2.0.0-alpha.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +491 -2
  3. package/dist/aws-ses.d.ts +30 -0
  4. package/dist/aws-ses.js +108 -0
  5. package/dist/aws-ses.js.map +1 -0
  6. package/dist/brevo.d.ts +23 -0
  7. package/dist/brevo.js +115 -0
  8. package/dist/brevo.js.map +1 -0
  9. package/dist/chunk-2ZABFOZQ.js +15 -0
  10. package/dist/chunk-2ZABFOZQ.js.map +1 -0
  11. package/dist/chunk-FRMKUAF4.js +88 -0
  12. package/dist/chunk-FRMKUAF4.js.map +1 -0
  13. package/dist/chunk-GOAXQ2GE.js +28 -0
  14. package/dist/chunk-GOAXQ2GE.js.map +1 -0
  15. package/dist/chunk-QYVN5N3B.js +221 -0
  16. package/dist/chunk-QYVN5N3B.js.map +1 -0
  17. package/dist/http-B35l9W1d.d.ts +9 -0
  18. package/dist/index.d.ts +97 -0
  19. package/dist/index.js +649 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/mailer-CmQCJ0qc.d.ts +339 -0
  22. package/dist/mailgun.d.ts +31 -0
  23. package/dist/mailgun.js +100 -0
  24. package/dist/mailgun.js.map +1 -0
  25. package/dist/mailjet.d.ts +28 -0
  26. package/dist/mailjet.js +104 -0
  27. package/dist/mailjet.js.map +1 -0
  28. package/dist/plugin.d.ts +33 -0
  29. package/dist/plugin.js +21 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/postmark.d.ts +27 -0
  32. package/dist/postmark.js +149 -0
  33. package/dist/postmark.js.map +1 -0
  34. package/dist/resend.d.ts +31 -0
  35. package/dist/resend.js +92 -0
  36. package/dist/resend.js.map +1 -0
  37. package/dist/scaleway.d.ts +31 -0
  38. package/dist/scaleway.js +113 -0
  39. package/dist/scaleway.js.map +1 -0
  40. package/dist/sendgrid.d.ts +26 -0
  41. package/dist/sendgrid.js +121 -0
  42. package/dist/sendgrid.js.map +1 -0
  43. package/dist/smtp.d.ts +45 -0
  44. package/dist/smtp.js +61 -0
  45. package/dist/smtp.js.map +1 -0
  46. package/package.json +139 -3
@@ -0,0 +1,104 @@
1
+ import {
2
+ checkHttpOptions,
3
+ checkSingleReplyTo,
4
+ jsonBody,
5
+ readJson,
6
+ responseError,
7
+ stringOf
8
+ } from "./chunk-FRMKUAF4.js";
9
+ import {
10
+ base64
11
+ } from "./chunk-2ZABFOZQ.js";
12
+ import {
13
+ MailSendError
14
+ } from "./chunk-QYVN5N3B.js";
15
+
16
+ // src/providers/mailjet.ts
17
+ var DEFAULT_BASE_URL = "https://api.mailjet.com";
18
+ var MAX_REQUEST_BYTES = 15e6;
19
+ var toAddress = ({ name, address }) => name ? { Email: address, Name: name } : { Email: address };
20
+ var toBody = (message) => {
21
+ checkSingleReplyTo("Mailjet", message.replyTo.length);
22
+ const { cc, bcc, replyTo, html, text, headers, metadata } = message;
23
+ const attachments = message.attachments.map(
24
+ ({ filename, content, contentType, contentId }) => ({
25
+ ContentType: contentType ?? "application/octet-stream",
26
+ Filename: filename,
27
+ Base64Content: base64(content),
28
+ ...contentId === void 0 ? {} : { ContentID: contentId }
29
+ })
30
+ );
31
+ const regular = attachments.filter((item) => !("ContentID" in item));
32
+ const inline = attachments.filter((item) => "ContentID" in item);
33
+ return {
34
+ Messages: [
35
+ {
36
+ From: toAddress(message.from),
37
+ To: message.to.map(toAddress),
38
+ ...cc.length > 0 ? { Cc: cc.map(toAddress) } : {},
39
+ ...bcc.length > 0 ? { Bcc: bcc.map(toAddress) } : {},
40
+ ...replyTo.length > 0 ? { ReplyTo: toAddress(replyTo[0]) } : {},
41
+ Subject: message.subject,
42
+ ...text === "" ? {} : { TextPart: text },
43
+ ...html === void 0 || html === "" ? {} : { HTMLPart: html },
44
+ ...Object.keys(headers).length > 0 ? { Headers: { ...headers } } : {},
45
+ ...regular.length > 0 ? { Attachments: regular } : {},
46
+ ...inline.length > 0 ? { InlinedAttachments: inline } : {},
47
+ // Mailjet has no tags; metadata is returned by the event webhooks.
48
+ ...Object.keys(metadata).length > 0 ? { EventPayload: JSON.stringify(metadata) } : {}
49
+ }
50
+ ]
51
+ };
52
+ };
53
+ var errorsOf = (result) => {
54
+ const [first] = result?.Messages ?? [];
55
+ const messages = (first?.Errors ?? []).map((error) => stringOf(error, "ErrorMessage")).filter((message) => message !== void 0);
56
+ if (messages.length > 0) return messages.join("; ");
57
+ return stringOf(result, "ErrorMessage");
58
+ };
59
+ var messageIdOf = (result) => {
60
+ const [first] = result?.Messages ?? [];
61
+ const recipient = first?.To?.[0];
62
+ if (typeof recipient?.MessageUUID === "string") return recipient.MessageUUID;
63
+ return typeof recipient?.MessageID === "number" ? String(recipient.MessageID) : void 0;
64
+ };
65
+ var createMailjetProvider = (options) => {
66
+ const { values, baseUrl, fetch } = checkHttpOptions(
67
+ "createMailjetProvider()",
68
+ options,
69
+ ["apiKey", "apiSecret"],
70
+ DEFAULT_BASE_URL
71
+ );
72
+ const url = `${baseUrl}/v3.1/send`;
73
+ const authorization = `Basic ${base64(`${values.apiKey}:${values.apiSecret}`)}`;
74
+ return {
75
+ name: "mailjet",
76
+ send: async (message) => {
77
+ const body = jsonBody("Mailjet", toBody(message), MAX_REQUEST_BYTES);
78
+ const response = await fetch(url, {
79
+ method: "POST",
80
+ headers: { authorization, "content-type": "application/json" },
81
+ body
82
+ });
83
+ const result = await readJson(response);
84
+ if (!response.ok) {
85
+ throw responseError("mailjet", response, errorsOf(result));
86
+ }
87
+ const [first] = result?.Messages ?? [];
88
+ if (first?.Status === "error") {
89
+ const code = first.Errors?.[0]?.StatusCode;
90
+ const status = typeof code === "number" ? code : void 0;
91
+ throw new MailSendError(
92
+ "mailjet",
93
+ `${status ?? ""} ${errorsOf(result) ?? "message rejected"}`.trim(),
94
+ { status }
95
+ );
96
+ }
97
+ return { messageId: messageIdOf(result) };
98
+ }
99
+ };
100
+ };
101
+ export {
102
+ createMailjetProvider
103
+ };
104
+ //# sourceMappingURL=mailjet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/mailjet.ts"],"sourcesContent":["import { base64 } from '../encoding.js'\nimport { MailSendError } from '../errors.js'\nimport type { MailProvider } from '../mailer.js'\nimport type { Address, MailMessage } from '../message.js'\nimport {\n checkHttpOptions,\n checkSingleReplyTo,\n jsonBody,\n readJson,\n responseError,\n stringOf,\n type HttpProviderOptions,\n} from './http.js'\n\n/** Options of `createMailjetProvider()`. */\nexport interface MailjetProviderOptions extends HttpProviderOptions {\n /** API key (public). */\n apiKey: string\n /** Secret key (private). */\n apiSecret: string\n /** Base URL of the API. Defaults to `https://api.mailjet.com`. */\n baseUrl?: string | undefined\n}\n\nconst DEFAULT_BASE_URL = 'https://api.mailjet.com'\nconst MAX_REQUEST_BYTES = 15_000_000\n\nconst toAddress = ({ name, address }: Address) =>\n name ? { Email: address, Name: name } : { Email: address }\n\n/** Body of the Send API v3.1, see https://dev.mailjet.com/email/reference/send-emails/ */\nconst toBody = (message: MailMessage) => {\n checkSingleReplyTo('Mailjet', message.replyTo.length)\n const { cc, bcc, replyTo, html, text, headers, metadata } = message\n const attachments = message.attachments.map(\n ({ filename, content, contentType, contentId }) => ({\n ContentType: contentType ?? 'application/octet-stream',\n Filename: filename,\n Base64Content: base64(content),\n ...(contentId === undefined ? {} : { ContentID: contentId }),\n })\n )\n const regular = attachments.filter((item) => !('ContentID' in item))\n const inline = attachments.filter((item) => 'ContentID' in item)\n return {\n Messages: [\n {\n From: toAddress(message.from),\n To: message.to.map(toAddress),\n ...(cc.length > 0 ? { Cc: cc.map(toAddress) } : {}),\n ...(bcc.length > 0 ? { Bcc: bcc.map(toAddress) } : {}),\n ...(replyTo.length > 0 ? { ReplyTo: toAddress(replyTo[0]!) } : {}),\n Subject: message.subject,\n ...(text === '' ? {} : { TextPart: text }),\n ...(html === undefined || html === '' ? {} : { HTMLPart: html }),\n ...(Object.keys(headers).length > 0 ? { Headers: { ...headers } } : {}),\n ...(regular.length > 0 ? { Attachments: regular } : {}),\n ...(inline.length > 0 ? { InlinedAttachments: inline } : {}),\n // Mailjet has no tags; metadata is returned by the event webhooks.\n ...(Object.keys(metadata).length > 0\n ? { EventPayload: JSON.stringify(metadata) }\n : {}),\n },\n ],\n }\n}\n\ninterface MailjetError {\n ErrorMessage?: unknown\n StatusCode?: unknown\n}\n\n/** Errors of the response, at the message level or at the top level. */\nconst errorsOf = (result: unknown): string | undefined => {\n const [first] =\n (result as { Messages?: { Errors?: MailjetError[] }[] } | undefined)\n ?.Messages ?? []\n const messages = (first?.Errors ?? [])\n .map((error) => stringOf(error, 'ErrorMessage'))\n .filter((message) => message !== undefined)\n if (messages.length > 0) return messages.join('; ')\n return stringOf(result, 'ErrorMessage')\n}\n\n/** Identifier of the message for the first recipient of `to`. */\nconst messageIdOf = (result: unknown) => {\n const [first] =\n (\n result as\n | {\n Messages?: {\n To?: { MessageUUID?: unknown; MessageID?: unknown }[]\n }[]\n }\n | undefined\n )?.Messages ?? []\n const recipient = first?.To?.[0]\n if (typeof recipient?.MessageUUID === 'string') return recipient.MessageUUID\n return typeof recipient?.MessageID === 'number'\n ? String(recipient.MessageID)\n : undefined\n}\n\n/**\n * Creates a provider that sends messages with the Send API v3.1 of\n * [Mailjet](https://www.mailjet.com), through `fetch`: no dependency.\n *\n * ```ts\n * const provider = createMailjetProvider({\n * apiKey: process.env.MAILJET_API_KEY!,\n * apiSecret: process.env.MAILJET_API_SECRET!,\n * })\n * ```\n */\nexport const createMailjetProvider = (\n options: MailjetProviderOptions\n): MailProvider => {\n const { values, baseUrl, fetch } = checkHttpOptions(\n 'createMailjetProvider()',\n options,\n ['apiKey', 'apiSecret'],\n DEFAULT_BASE_URL\n )\n const url = `${baseUrl}/v3.1/send`\n const authorization = `Basic ${base64(`${values.apiKey}:${values.apiSecret}`)}`\n\n return {\n name: 'mailjet',\n send: async (message) => {\n const body = jsonBody('Mailjet', toBody(message), MAX_REQUEST_BYTES)\n const response = await fetch(url, {\n method: 'POST',\n headers: { authorization, 'content-type': 'application/json' },\n body,\n })\n const result = await readJson(response)\n if (!response.ok) {\n throw responseError('mailjet', response, errorsOf(result))\n }\n const [first] =\n (\n result as\n | { Messages?: { Status?: unknown; Errors?: MailjetError[] }[] }\n | undefined\n )?.Messages ?? []\n // A message can fail in a successful response.\n if (first?.Status === 'error') {\n const code = first.Errors?.[0]?.StatusCode\n const status = typeof code === 'number' ? code : undefined\n throw new MailSendError(\n 'mailjet',\n `${status ?? ''} ${errorsOf(result) ?? 'message rejected'}`.trim(),\n { status }\n )\n }\n return { messageId: messageIdOf(result) }\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,33 @@
1
+ import * as _dotpress_core from '@dotpress/core';
2
+ import { Mailer } from './index.js';
3
+ import 'zod';
4
+
5
+ declare module '@dotpress/core' {
6
+ interface RequestContext {
7
+ /** Mailer (`mailerPlugin`), logging with the request logger. */
8
+ mailer: Mailer;
9
+ }
10
+ }
11
+ /**
12
+ * Provides `ctx.mailer` to routes and middlewares. Sent messages are
13
+ * logged with the request logger (request ID).
14
+ *
15
+ * ```ts
16
+ * await createApp({ adapter, plugins: [mailerPlugin(mailer)] })
17
+ *
18
+ * defineRoute({
19
+ * method: 'post',
20
+ * path: '/invitations',
21
+ * handler: async ({ mailer, body }) => {
22
+ * await mailer.send(invitation, { to: body.email, data: { … } })
23
+ * },
24
+ * })
25
+ * ```
26
+ */
27
+ declare const mailerPlugin: (mailer: Mailer) => {
28
+ readonly name: "mailer";
29
+ readonly mailer: Mailer;
30
+ readonly setup: (api: _dotpress_core.PluginAPI) => void;
31
+ };
32
+
33
+ export { mailerPlugin };
package/dist/plugin.js ADDED
@@ -0,0 +1,21 @@
1
+ // src/plugin.ts
2
+ import { declareContextKey, definePlugin } from "@dotpress/core";
3
+ declareContextKey("mailer", "mailerPlugin");
4
+ var mailerPlugin = (mailer) => {
5
+ if (typeof mailer?.withLogger !== "function") {
6
+ throw new TypeError(
7
+ "mailerPlugin(): expected a mailer created by createMailer()."
8
+ );
9
+ }
10
+ return definePlugin({
11
+ name: "mailer",
12
+ mailer,
13
+ setup(api) {
14
+ api.decorateContext("mailer", (ctx) => mailer.withLogger(ctx.logger));
15
+ }
16
+ });
17
+ };
18
+ export {
19
+ mailerPlugin
20
+ };
21
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { declareContextKey, definePlugin } from '@dotpress/core'\nimport type { Mailer } from './mailer.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Mailer (`mailerPlugin`), logging with the request logger. */\n mailer: Mailer\n }\n}\n\n// Accessing ctx.mailer without the plugin throws an explicit error.\ndeclareContextKey('mailer', 'mailerPlugin')\n\n/**\n * Provides `ctx.mailer` to routes and middlewares. Sent messages are\n * logged with the request logger (request ID).\n *\n * ```ts\n * await createApp({ adapter, plugins: [mailerPlugin(mailer)] })\n *\n * defineRoute({\n * method: 'post',\n * path: '/invitations',\n * handler: async ({ mailer, body }) => {\n * await mailer.send(invitation, { to: body.email, data: { … } })\n * },\n * })\n * ```\n */\nexport const mailerPlugin = (mailer: Mailer) => {\n if (typeof mailer?.withLogger !== 'function') {\n throw new TypeError(\n 'mailerPlugin(): expected a mailer created by createMailer().'\n )\n }\n return definePlugin({\n name: 'mailer',\n mailer,\n setup(api) {\n api.decorateContext('mailer', (ctx) => mailer.withLogger(ctx.logger))\n },\n })\n}\n"],"mappings":";AAAA,SAAS,mBAAmB,oBAAoB;AAWhD,kBAAkB,UAAU,cAAc;AAkBnC,IAAM,eAAe,CAAC,WAAmB;AAC9C,MAAI,OAAO,QAAQ,eAAe,YAAY;AAC5C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN;AAAA,IACA,MAAM,KAAK;AACT,UAAI,gBAAgB,UAAU,CAAC,QAAQ,OAAO,WAAW,IAAI,MAAM,CAAC;AAAA,IACtE;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -0,0 +1,27 @@
1
+ import { MailProvider } from './index.js';
2
+ import { HttpProviderOptions } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Options of `createPostmarkProvider()`. */
6
+ interface PostmarkProviderOptions extends HttpProviderOptions {
7
+ /** Server API token. `POSTMARK_API_TEST` sends nothing (test mode). */
8
+ serverToken: string;
9
+ /** Message stream. Defaults to the transactional stream (`outbound`). */
10
+ messageStream?: string | undefined;
11
+ /** Base URL of the API. Defaults to `https://api.postmarkapp.com`. */
12
+ baseUrl?: string | undefined;
13
+ }
14
+ /**
15
+ * Creates a provider that sends messages with the Email API of
16
+ * [Postmark](https://postmarkapp.com), through `fetch`: no dependency.
17
+ *
18
+ * ```ts
19
+ * const provider = createPostmarkProvider({
20
+ * serverToken: process.env.POSTMARK_SERVER_TOKEN!,
21
+ * })
22
+ * ```
23
+ */
24
+ declare const createPostmarkProvider: (options: PostmarkProviderOptions) => MailProvider;
25
+
26
+ export { createPostmarkProvider };
27
+ export type { PostmarkProviderOptions };
@@ -0,0 +1,149 @@
1
+ import {
2
+ checkHttpOptions,
3
+ jsonBody,
4
+ readJson,
5
+ responseError,
6
+ stringOf
7
+ } from "./chunk-FRMKUAF4.js";
8
+ import {
9
+ base64
10
+ } from "./chunk-2ZABFOZQ.js";
11
+ import {
12
+ MailMessageError,
13
+ formatAddress
14
+ } from "./chunk-QYVN5N3B.js";
15
+
16
+ // src/providers/postmark.ts
17
+ var DEFAULT_BASE_URL = "https://api.postmarkapp.com";
18
+ var MAX_RECIPIENTS = 50;
19
+ var MAX_ADDRESS_LENGTH = 254;
20
+ var MAX_FROM_LENGTH = 255;
21
+ var MAX_SUBJECT_LENGTH = 2e3;
22
+ var MAX_REQUEST_BYTES = 1e7;
23
+ var FORBIDDEN_EXTENSIONS = new Set(
24
+ "vbs exe bin bat chm com cpl crt hlp hta inf ins isp jse lnk mdb pcd pif reg scr sct shs vbe vba wsf wsh wsl msc msi msp mst".split(
25
+ " "
26
+ )
27
+ );
28
+ var fail = (message) => {
29
+ throw new MailMessageError(`Postmark ${message}`);
30
+ };
31
+ var checkLimits = (message) => {
32
+ const recipients = [...message.to, ...message.cc, ...message.bcc];
33
+ if (recipients.length > MAX_RECIPIENTS) {
34
+ fail(
35
+ `accepts at most ${MAX_RECIPIENTS} recipients (to, cc and bcc), got ${recipients.length}.`
36
+ );
37
+ }
38
+ const long = [...recipients, ...message.replyTo].find(
39
+ ({ address }) => address.length > MAX_ADDRESS_LENGTH
40
+ );
41
+ if (long) {
42
+ fail(
43
+ `accepts addresses of at most ${MAX_ADDRESS_LENGTH} characters: ${long.address.slice(0, 20)}\u2026 has ${long.address.length}.`
44
+ );
45
+ }
46
+ if (formatAddress(message.from).length > MAX_FROM_LENGTH) {
47
+ fail(`accepts a sender of at most ${MAX_FROM_LENGTH} characters.`);
48
+ }
49
+ if (message.subject.length > MAX_SUBJECT_LENGTH) {
50
+ fail(
51
+ `accepts subjects of at most ${MAX_SUBJECT_LENGTH} characters, got ${message.subject.length}.`
52
+ );
53
+ }
54
+ if (message.tags.length > 1) {
55
+ fail(`accepts a single tag, got ${message.tags.length}.`);
56
+ }
57
+ for (const { filename } of message.attachments) {
58
+ const extension = /\.([^.]+)$/.exec(filename)?.[1]?.toLowerCase();
59
+ if (extension && FORBIDDEN_EXTENSIONS.has(extension)) {
60
+ fail(`does not accept .${extension} attachments (${filename}).`);
61
+ }
62
+ }
63
+ };
64
+ var list = (addresses) => addresses.map(formatAddress).join(", ");
65
+ var toBody = (message, messageStream) => {
66
+ checkLimits(message);
67
+ const { cc, bcc, replyTo, html, text, attachments, headers } = message;
68
+ const [tag] = message.tags;
69
+ return {
70
+ From: formatAddress(message.from),
71
+ To: list(message.to),
72
+ ...cc.length > 0 ? { Cc: list(cc) } : {},
73
+ ...bcc.length > 0 ? { Bcc: list(bcc) } : {},
74
+ ...replyTo.length > 0 ? { ReplyTo: list(replyTo) } : {},
75
+ Subject: message.subject,
76
+ ...text === "" ? {} : { TextBody: text },
77
+ ...html === void 0 || html === "" ? {} : { HtmlBody: html },
78
+ ...Object.keys(headers).length > 0 ? {
79
+ Headers: Object.entries(headers).map(([Name, Value]) => ({
80
+ Name,
81
+ Value
82
+ }))
83
+ } : {},
84
+ ...attachments.length > 0 ? {
85
+ Attachments: attachments.map(
86
+ ({ filename, content, contentType, contentId }) => ({
87
+ Name: filename,
88
+ Content: base64(content),
89
+ ContentType: contentType ?? "application/octet-stream",
90
+ ...contentId === void 0 ? {} : { ContentID: `cid:${contentId}` }
91
+ })
92
+ )
93
+ } : {},
94
+ // Left out of the JSON when undefined.
95
+ Tag: tag,
96
+ ...Object.keys(message.metadata).length > 0 ? { Metadata: { ...message.metadata } } : {},
97
+ ...messageStream === void 0 ? {} : { MessageStream: messageStream }
98
+ };
99
+ };
100
+ var createPostmarkProvider = (options) => {
101
+ const { values, baseUrl, fetch } = checkHttpOptions(
102
+ "createPostmarkProvider()",
103
+ options,
104
+ ["serverToken"],
105
+ DEFAULT_BASE_URL,
106
+ ["messageStream"]
107
+ );
108
+ const { serverToken, messageStream } = values;
109
+ if (messageStream !== void 0 && (typeof messageStream !== "string" || messageStream === "")) {
110
+ throw new TypeError(
111
+ "createPostmarkProvider(): messageStream must be a non-empty string."
112
+ );
113
+ }
114
+ const url = `${baseUrl}/email`;
115
+ return {
116
+ name: "postmark",
117
+ send: async (message) => {
118
+ const body = jsonBody(
119
+ "Postmark",
120
+ toBody(message, messageStream),
121
+ MAX_REQUEST_BYTES
122
+ );
123
+ const response = await fetch(url, {
124
+ method: "POST",
125
+ headers: {
126
+ accept: "application/json",
127
+ "content-type": "application/json",
128
+ "x-postmark-server-token": serverToken
129
+ },
130
+ body
131
+ });
132
+ const result = await readJson(response);
133
+ if (!response.ok) {
134
+ const detail = stringOf(result, "Message");
135
+ const code = result?.ErrorCode;
136
+ throw responseError(
137
+ "postmark",
138
+ response,
139
+ detail && typeof code === "number" ? `${detail} (ErrorCode ${code})` : detail
140
+ );
141
+ }
142
+ return { messageId: stringOf(result, "MessageID") };
143
+ }
144
+ };
145
+ };
146
+ export {
147
+ createPostmarkProvider
148
+ };
149
+ //# sourceMappingURL=postmark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/postmark.ts"],"sourcesContent":["import { base64 } from '../encoding.js'\nimport { MailMessageError } from '../errors.js'\nimport type { MailProvider } from '../mailer.js'\nimport { formatAddress, type Address, type MailMessage } from '../message.js'\nimport {\n checkHttpOptions,\n jsonBody,\n readJson,\n responseError,\n stringOf,\n type HttpProviderOptions,\n} from './http.js'\n\n/** Options of `createPostmarkProvider()`. */\nexport interface PostmarkProviderOptions extends HttpProviderOptions {\n /** Server API token. `POSTMARK_API_TEST` sends nothing (test mode). */\n serverToken: string\n /** Message stream. Defaults to the transactional stream (`outbound`). */\n messageStream?: string | undefined\n /** Base URL of the API. Defaults to `https://api.postmarkapp.com`. */\n baseUrl?: string | undefined\n}\n\nconst DEFAULT_BASE_URL = 'https://api.postmarkapp.com'\n\n// Limits of the Email API.\nconst MAX_RECIPIENTS = 50\nconst MAX_ADDRESS_LENGTH = 254\nconst MAX_FROM_LENGTH = 255\nconst MAX_SUBJECT_LENGTH = 2000\nconst MAX_REQUEST_BYTES = 10_000_000\nconst FORBIDDEN_EXTENSIONS = new Set(\n 'vbs exe bin bat chm com cpl crt hlp hta inf ins isp jse lnk mdb pcd pif reg scr sct shs vbe vba wsf wsh wsl msc msi msp mst'.split(\n ' '\n )\n)\n\nconst fail = (message: string): never => {\n throw new MailMessageError(`Postmark ${message}`)\n}\n\n/** Checks the limits of the Email API. */\nconst checkLimits = (message: MailMessage) => {\n const recipients = [...message.to, ...message.cc, ...message.bcc]\n if (recipients.length > MAX_RECIPIENTS) {\n fail(\n `accepts at most ${MAX_RECIPIENTS} recipients (to, cc and bcc), got ${recipients.length}.`\n )\n }\n const long = [...recipients, ...message.replyTo].find(\n ({ address }) => address.length > MAX_ADDRESS_LENGTH\n )\n if (long) {\n fail(\n `accepts addresses of at most ${MAX_ADDRESS_LENGTH} characters: ${long.address.slice(0, 20)}… has ${long.address.length}.`\n )\n }\n if (formatAddress(message.from).length > MAX_FROM_LENGTH) {\n fail(`accepts a sender of at most ${MAX_FROM_LENGTH} characters.`)\n }\n if (message.subject.length > MAX_SUBJECT_LENGTH) {\n fail(\n `accepts subjects of at most ${MAX_SUBJECT_LENGTH} characters, got ${message.subject.length}.`\n )\n }\n if (message.tags.length > 1) {\n fail(`accepts a single tag, got ${message.tags.length}.`)\n }\n for (const { filename } of message.attachments) {\n const extension = /\\.([^.]+)$/.exec(filename)?.[1]?.toLowerCase()\n if (extension && FORBIDDEN_EXTENSIONS.has(extension)) {\n fail(`does not accept .${extension} attachments (${filename}).`)\n }\n }\n}\n\nconst list = (addresses: readonly Address[]) =>\n addresses.map(formatAddress).join(', ')\n\n/** Body of the Email API, see https://postmarkapp.com/developer/api/email-api */\nconst toBody = (message: MailMessage, messageStream: string | undefined) => {\n checkLimits(message)\n const { cc, bcc, replyTo, html, text, attachments, headers } = message\n const [tag] = message.tags\n return {\n From: formatAddress(message.from),\n To: list(message.to),\n ...(cc.length > 0 ? { Cc: list(cc) } : {}),\n ...(bcc.length > 0 ? { Bcc: list(bcc) } : {}),\n ...(replyTo.length > 0 ? { ReplyTo: list(replyTo) } : {}),\n Subject: message.subject,\n ...(text === '' ? {} : { TextBody: text }),\n ...(html === undefined || html === '' ? {} : { HtmlBody: html }),\n ...(Object.keys(headers).length > 0\n ? {\n Headers: Object.entries(headers).map(([Name, Value]) => ({\n Name,\n Value,\n })),\n }\n : {}),\n ...(attachments.length > 0\n ? {\n Attachments: attachments.map(\n ({ filename, content, contentType, contentId }) => ({\n Name: filename,\n Content: base64(content),\n ContentType: contentType ?? 'application/octet-stream',\n ...(contentId === undefined\n ? {}\n : { ContentID: `cid:${contentId}` }),\n })\n ),\n }\n : {}),\n // Left out of the JSON when undefined.\n Tag: tag,\n ...(Object.keys(message.metadata).length > 0\n ? { Metadata: { ...message.metadata } }\n : {}),\n ...(messageStream === undefined ? {} : { MessageStream: messageStream }),\n }\n}\n\n/**\n * Creates a provider that sends messages with the Email API of\n * [Postmark](https://postmarkapp.com), through `fetch`: no dependency.\n *\n * ```ts\n * const provider = createPostmarkProvider({\n * serverToken: process.env.POSTMARK_SERVER_TOKEN!,\n * })\n * ```\n */\nexport const createPostmarkProvider = (\n options: PostmarkProviderOptions\n): MailProvider => {\n const { values, baseUrl, fetch } = checkHttpOptions(\n 'createPostmarkProvider()',\n options,\n ['serverToken'],\n DEFAULT_BASE_URL,\n ['messageStream']\n )\n const { serverToken, messageStream } = values\n if (\n messageStream !== undefined &&\n (typeof messageStream !== 'string' || messageStream === '')\n ) {\n throw new TypeError(\n 'createPostmarkProvider(): messageStream must be a non-empty string.'\n )\n }\n const url = `${baseUrl}/email`\n\n return {\n name: 'postmark',\n send: async (message) => {\n const body = jsonBody(\n 'Postmark',\n toBody(message, messageStream as string | undefined),\n MAX_REQUEST_BYTES\n )\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n 'x-postmark-server-token': serverToken,\n },\n body,\n })\n const result = await readJson(response)\n if (!response.ok) {\n const detail = stringOf(result, 'Message')\n const code = (result as { ErrorCode?: unknown } | undefined)?.ErrorCode\n throw responseError(\n 'postmark',\n response,\n detail && typeof code === 'number'\n ? `${detail} (ErrorCode ${code})`\n : detail\n )\n }\n return { messageId: stringOf(result, 'MessageID') }\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,31 @@
1
+ import { CreateEmailOptions, CreateEmailResponse } from 'resend';
2
+ import { MailProvider } from './index.js';
3
+ import 'zod';
4
+
5
+ /** A Resend client, or any object with the same `emails.send()` method. */
6
+ interface ResendClient {
7
+ emails: {
8
+ send(payload: CreateEmailOptions): Promise<CreateEmailResponse>;
9
+ };
10
+ }
11
+ /** Options of `createResendProvider()`. */
12
+ interface ResendProviderOptions {
13
+ /** API key. Defaults to the `RESEND_API_KEY` environment variable. */
14
+ apiKey?: string | undefined;
15
+ /** Base URL of the API, for the created client. */
16
+ baseUrl?: string | undefined;
17
+ /** Existing client, instead of `apiKey` and `baseUrl`. */
18
+ client?: ResendClient | undefined;
19
+ }
20
+ /**
21
+ * Creates a provider that sends messages with [Resend](https://resend.com),
22
+ * through its SDK (`resend`, peer dependency).
23
+ *
24
+ * ```ts
25
+ * const provider = createResendProvider({ apiKey: process.env.RESEND_API_KEY })
26
+ * ```
27
+ */
28
+ declare const createResendProvider: (options?: ResendProviderOptions) => MailProvider;
29
+
30
+ export { createResendProvider };
31
+ export type { ResendClient, ResendProviderOptions };
package/dist/resend.js ADDED
@@ -0,0 +1,92 @@
1
+ import {
2
+ keyValueTags
3
+ } from "./chunk-GOAXQ2GE.js";
4
+ import {
5
+ base64
6
+ } from "./chunk-2ZABFOZQ.js";
7
+ import {
8
+ MailSendError,
9
+ checkKeys,
10
+ formatAddress
11
+ } from "./chunk-QYVN5N3B.js";
12
+
13
+ // src/providers/resend.ts
14
+ import {
15
+ Resend
16
+ } from "resend";
17
+ var OPTIONS = /* @__PURE__ */ new Set(["apiKey", "baseUrl", "client"]);
18
+ var isNonEmptyString = (value) => typeof value === "string" && value !== "";
19
+ var tagsOf = (message) => {
20
+ const tags = keyValueTags(message, "Resend");
21
+ return tags.length > 0 ? { tags } : {};
22
+ };
23
+ var toPayload = (message) => ({
24
+ from: formatAddress(message.from),
25
+ to: message.to.map(formatAddress),
26
+ ...message.cc.length > 0 ? { cc: message.cc.map(formatAddress) } : {},
27
+ ...message.bcc.length > 0 ? { bcc: message.bcc.map(formatAddress) } : {},
28
+ ...message.replyTo.length > 0 ? { replyTo: message.replyTo.map(formatAddress) } : {},
29
+ subject: message.subject,
30
+ text: message.text,
31
+ ...message.html === void 0 ? {} : { html: message.html },
32
+ ...message.attachments.length > 0 ? {
33
+ attachments: message.attachments.map(
34
+ ({ filename, content, contentType, contentId }) => ({
35
+ filename,
36
+ // Base64 works with every version of the SDK.
37
+ content: base64(content),
38
+ ...contentType === void 0 ? {} : { contentType },
39
+ ...contentId === void 0 ? {} : { contentId }
40
+ })
41
+ )
42
+ } : {},
43
+ ...Object.keys(message.headers).length > 0 ? { headers: { ...message.headers } } : {},
44
+ ...tagsOf(message)
45
+ });
46
+ var createResendProvider = (options = {}) => {
47
+ const { apiKey, baseUrl, client } = checkKeys(
48
+ "createResendProvider()",
49
+ options,
50
+ OPTIONS
51
+ );
52
+ if (client !== void 0) {
53
+ if (apiKey !== void 0 || baseUrl !== void 0) {
54
+ throw new TypeError(
55
+ "createResendProvider(): set client, or apiKey and baseUrl, not both."
56
+ );
57
+ }
58
+ if (typeof client?.emails?.send !== "function") {
59
+ throw new TypeError(
60
+ "createResendProvider(): client must have an emails.send() method."
61
+ );
62
+ }
63
+ }
64
+ for (const [key, value] of [
65
+ ["apiKey", apiKey],
66
+ ["baseUrl", baseUrl]
67
+ ]) {
68
+ if (value !== void 0 && !isNonEmptyString(value)) {
69
+ throw new TypeError(
70
+ `createResendProvider(): ${key} must be a non-empty string.`
71
+ );
72
+ }
73
+ }
74
+ const resend = client ?? new Resend(apiKey, baseUrl === void 0 ? void 0 : { baseUrl });
75
+ return {
76
+ name: "resend",
77
+ send: async (message) => {
78
+ const { data, error } = await resend.emails.send(toPayload(message));
79
+ if (error) {
80
+ throw new MailSendError("resend", `${error.name}: ${error.message}`, {
81
+ status: error.statusCode ?? void 0,
82
+ cause: error
83
+ });
84
+ }
85
+ return { messageId: data?.id };
86
+ }
87
+ };
88
+ };
89
+ export {
90
+ createResendProvider
91
+ };
92
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/resend.ts"],"sourcesContent":["import {\n Resend,\n type CreateEmailOptions,\n type CreateEmailResponse,\n} from 'resend'\nimport { base64 } from '../encoding.js'\nimport { MailSendError } from '../errors.js'\nimport type { MailProvider } from '../mailer.js'\nimport { checkKeys, formatAddress, type MailMessage } from '../message.js'\nimport { keyValueTags } from './key-value-tags.js'\n\n/** A Resend client, or any object with the same `emails.send()` method. */\nexport interface ResendClient {\n emails: {\n send(payload: CreateEmailOptions): Promise<CreateEmailResponse>\n }\n}\n\n/** Options of `createResendProvider()`. */\nexport interface ResendProviderOptions {\n /** API key. Defaults to the `RESEND_API_KEY` environment variable. */\n apiKey?: string | undefined\n /** Base URL of the API, for the created client. */\n baseUrl?: string | undefined\n /** Existing client, instead of `apiKey` and `baseUrl`. */\n client?: ResendClient | undefined\n}\n\nconst OPTIONS = new Set(['apiKey', 'baseUrl', 'client'])\n\nconst isNonEmptyString = (value: unknown) =>\n typeof value === 'string' && value !== ''\n\n/** Tags and metadata as Resend tags. */\nconst tagsOf = (message: MailMessage) => {\n const tags = keyValueTags(message, 'Resend')\n return tags.length > 0 ? { tags } : {}\n}\n\n/** Payload of the Resend API. */\nconst toPayload = (message: MailMessage): CreateEmailOptions => ({\n from: formatAddress(message.from),\n to: message.to.map(formatAddress),\n ...(message.cc.length > 0 ? { cc: message.cc.map(formatAddress) } : {}),\n ...(message.bcc.length > 0 ? { bcc: message.bcc.map(formatAddress) } : {}),\n ...(message.replyTo.length > 0\n ? { replyTo: message.replyTo.map(formatAddress) }\n : {}),\n subject: message.subject,\n text: message.text,\n ...(message.html === undefined ? {} : { html: message.html }),\n ...(message.attachments.length > 0\n ? {\n attachments: message.attachments.map(\n ({ filename, content, contentType, contentId }) => ({\n filename,\n // Base64 works with every version of the SDK.\n content: base64(content),\n ...(contentType === undefined ? {} : { contentType }),\n ...(contentId === undefined ? {} : { contentId }),\n })\n ),\n }\n : {}),\n ...(Object.keys(message.headers).length > 0\n ? { headers: { ...message.headers } }\n : {}),\n ...tagsOf(message),\n})\n\n/**\n * Creates a provider that sends messages with [Resend](https://resend.com),\n * through its SDK (`resend`, peer dependency).\n *\n * ```ts\n * const provider = createResendProvider({ apiKey: process.env.RESEND_API_KEY })\n * ```\n */\nexport const createResendProvider = (\n options: ResendProviderOptions = {}\n): MailProvider => {\n const { apiKey, baseUrl, client } = checkKeys(\n 'createResendProvider()',\n options,\n OPTIONS\n ) as ResendProviderOptions\n if (client !== undefined) {\n if (apiKey !== undefined || baseUrl !== undefined) {\n throw new TypeError(\n 'createResendProvider(): set client, or apiKey and baseUrl, not both.'\n )\n }\n if (typeof client?.emails?.send !== 'function') {\n throw new TypeError(\n 'createResendProvider(): client must have an emails.send() method.'\n )\n }\n }\n for (const [key, value] of [\n ['apiKey', apiKey],\n ['baseUrl', baseUrl],\n ] as const) {\n if (value !== undefined && !isNonEmptyString(value)) {\n throw new TypeError(\n `createResendProvider(): ${key} must be a non-empty string.`\n )\n }\n }\n\n const resend =\n client ??\n new Resend(apiKey, baseUrl === undefined ? undefined : { baseUrl })\n\n return {\n name: 'resend',\n send: async (message) => {\n const { data, error } = await resend.emails.send(toPayload(message))\n if (error) {\n throw new MailSendError('resend', `${error.name}: ${error.message}`, {\n status: error.statusCode ?? undefined,\n cause: error,\n })\n }\n return { messageId: data?.id }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,OAGK;AAwBP,IAAM,UAAU,oBAAI,IAAI,CAAC,UAAU,WAAW,QAAQ,CAAC;AAEvD,IAAM,mBAAmB,CAAC,UACxB,OAAO,UAAU,YAAY,UAAU;AAGzC,IAAM,SAAS,CAAC,YAAyB;AACvC,QAAM,OAAO,aAAa,SAAS,QAAQ;AAC3C,SAAO,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AACvC;AAGA,IAAM,YAAY,CAAC,aAA8C;AAAA,EAC/D,MAAM,cAAc,QAAQ,IAAI;AAAA,EAChC,IAAI,QAAQ,GAAG,IAAI,aAAa;AAAA,EAChC,GAAI,QAAQ,GAAG,SAAS,IAAI,EAAE,IAAI,QAAQ,GAAG,IAAI,aAAa,EAAE,IAAI,CAAC;AAAA,EACrE,GAAI,QAAQ,IAAI,SAAS,IAAI,EAAE,KAAK,QAAQ,IAAI,IAAI,aAAa,EAAE,IAAI,CAAC;AAAA,EACxE,GAAI,QAAQ,QAAQ,SAAS,IACzB,EAAE,SAAS,QAAQ,QAAQ,IAAI,aAAa,EAAE,IAC9C,CAAC;AAAA,EACL,SAAS,QAAQ;AAAA,EACjB,MAAM,QAAQ;AAAA,EACd,GAAI,QAAQ,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;AAAA,EAC3D,GAAI,QAAQ,YAAY,SAAS,IAC7B;AAAA,IACE,aAAa,QAAQ,YAAY;AAAA,MAC/B,CAAC,EAAE,UAAU,SAAS,aAAa,UAAU,OAAO;AAAA,QAClD;AAAA;AAAA,QAEA,SAAS,OAAO,OAAO;AAAA,QACvB,GAAI,gBAAgB,SAAY,CAAC,IAAI,EAAE,YAAY;AAAA,QACnD,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,UAAU;AAAA,MACjD;AAAA,IACF;AAAA,EACF,IACA,CAAC;AAAA,EACL,GAAI,OAAO,KAAK,QAAQ,OAAO,EAAE,SAAS,IACtC,EAAE,SAAS,EAAE,GAAG,QAAQ,QAAQ,EAAE,IAClC,CAAC;AAAA,EACL,GAAG,OAAO,OAAO;AACnB;AAUO,IAAM,uBAAuB,CAClC,UAAiC,CAAC,MACjB;AACjB,QAAM,EAAE,QAAQ,SAAS,OAAO,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,WAAW,QAAW;AACxB,QAAI,WAAW,UAAa,YAAY,QAAW;AACjD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAO,QAAQ,QAAQ,SAAS,YAAY;AAC9C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,aAAW,CAAC,KAAK,KAAK,KAAK;AAAA,IACzB,CAAC,UAAU,MAAM;AAAA,IACjB,CAAC,WAAW,OAAO;AAAA,EACrB,GAAY;AACV,QAAI,UAAU,UAAa,CAAC,iBAAiB,KAAK,GAAG;AACnD,YAAM,IAAI;AAAA,QACR,2BAA2B,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SACJ,UACA,IAAI,OAAO,QAAQ,YAAY,SAAY,SAAY,EAAE,QAAQ,CAAC;AAEpE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,OAAO,YAAY;AACvB,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,OAAO,KAAK,UAAU,OAAO,CAAC;AACnE,UAAI,OAAO;AACT,cAAM,IAAI,cAAc,UAAU,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,IAAI;AAAA,UACnE,QAAQ,MAAM,cAAc;AAAA,UAC5B,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AACA,aAAO,EAAE,WAAW,MAAM,GAAG;AAAA,IAC/B;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,31 @@
1
+ import { MailProvider } from './index.js';
2
+ import { HttpProviderOptions } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Options of `createScalewayProvider()`. */
6
+ interface ScalewayProviderOptions extends HttpProviderOptions {
7
+ /** Secret key of an API key with access to Transactional Email. */
8
+ secretKey: string;
9
+ /** ID of the Project of the sending domain. */
10
+ projectId: string;
11
+ /** Region of the sending domain. Defaults to `fr-par`. */
12
+ region?: string | undefined;
13
+ /** Base URL of the API. Defaults to `https://api.scaleway.com`. */
14
+ baseUrl?: string | undefined;
15
+ }
16
+ /**
17
+ * Creates a provider that sends messages with
18
+ * [Scaleway Transactional Email](https://www.scaleway.com/en/transactional-email-tem/),
19
+ * through `fetch`: no dependency.
20
+ *
21
+ * ```ts
22
+ * const provider = createScalewayProvider({
23
+ * secretKey: process.env.SCW_SECRET_KEY!,
24
+ * projectId: process.env.SCW_DEFAULT_PROJECT_ID!,
25
+ * })
26
+ * ```
27
+ */
28
+ declare const createScalewayProvider: (options: ScalewayProviderOptions) => MailProvider;
29
+
30
+ export { createScalewayProvider };
31
+ export type { ScalewayProviderOptions };
@@ -0,0 +1,113 @@
1
+ import {
2
+ checkHttpOptions,
3
+ checkNoInline,
4
+ readJson,
5
+ responseError,
6
+ stringOf
7
+ } from "./chunk-FRMKUAF4.js";
8
+ import {
9
+ base64
10
+ } from "./chunk-2ZABFOZQ.js";
11
+ import {
12
+ MailMessageError,
13
+ formatAddress
14
+ } from "./chunk-QYVN5N3B.js";
15
+
16
+ // src/providers/scaleway.ts
17
+ var DEFAULT_BASE_URL = "https://api.scaleway.com";
18
+ var DEFAULT_REGION = "fr-par";
19
+ var MAX_SUBJECT_LENGTH = 998;
20
+ var toAddress = ({ name, address }) => name ? { email: address, name } : { email: address };
21
+ var checkLimits = (message) => {
22
+ checkNoInline("Scaleway", message.attachments);
23
+ if (message.subject.length > MAX_SUBJECT_LENGTH) {
24
+ throw new MailMessageError(
25
+ `Scaleway accepts subjects of at most ${MAX_SUBJECT_LENGTH} characters, got ${message.subject.length}.`
26
+ );
27
+ }
28
+ const empty = Object.entries(message.headers).find(
29
+ ([, value]) => value === ""
30
+ );
31
+ if (empty) {
32
+ throw new MailMessageError(
33
+ `Scaleway does not accept empty header values (${empty[0]}).`
34
+ );
35
+ }
36
+ };
37
+ var toBody = (message, projectId) => {
38
+ checkLimits(message);
39
+ const { cc, bcc, replyTo, attachments } = message;
40
+ const headers = [
41
+ ...replyTo.length > 0 ? [{ key: "Reply-To", value: replyTo.map(formatAddress).join(", ") }] : [],
42
+ ...Object.entries(message.headers).map(([key, value]) => ({ key, value }))
43
+ ];
44
+ return {
45
+ from: toAddress(message.from),
46
+ to: message.to.map(toAddress),
47
+ ...cc.length > 0 ? { cc: cc.map(toAddress) } : {},
48
+ ...bcc.length > 0 ? { bcc: bcc.map(toAddress) } : {},
49
+ subject: message.subject,
50
+ text: message.text,
51
+ html: message.html ?? "",
52
+ project_id: projectId,
53
+ ...attachments.length > 0 ? {
54
+ attachments: attachments.map(
55
+ ({ filename, content, contentType }) => ({
56
+ name: filename,
57
+ type: contentType ?? "application/octet-stream",
58
+ content: base64(content)
59
+ })
60
+ )
61
+ } : {},
62
+ ...headers.length > 0 ? { additional_headers: headers } : {}
63
+ };
64
+ };
65
+ var errorDetail = (result) => {
66
+ const message = stringOf(result, "message");
67
+ const fields = result?.fields;
68
+ if (message === void 0 || typeof fields !== "object" || fields === null) {
69
+ return message;
70
+ }
71
+ const details = Object.entries(fields).filter(([, list]) => Array.isArray(list)).map(([name, list]) => `${name} (${list.join(", ")})`);
72
+ return details.length > 0 ? `${message}: ${details.join(", ")}` : message;
73
+ };
74
+ var createScalewayProvider = (options) => {
75
+ const { values, baseUrl, fetch } = checkHttpOptions(
76
+ "createScalewayProvider()",
77
+ options,
78
+ ["secretKey", "projectId"],
79
+ DEFAULT_BASE_URL,
80
+ ["region"]
81
+ );
82
+ const { region = DEFAULT_REGION } = values;
83
+ if (typeof region !== "string" || !/^[a-z]{2}-[a-z]{3}$/.test(region)) {
84
+ throw new TypeError(
85
+ "createScalewayProvider(): region must be a Scaleway region, like fr-par."
86
+ );
87
+ }
88
+ const url = `${baseUrl}/transactional-email/v1alpha1/regions/${region}/emails`;
89
+ return {
90
+ name: "scaleway",
91
+ send: async (message) => {
92
+ const body = JSON.stringify(toBody(message, values.projectId));
93
+ const response = await fetch(url, {
94
+ method: "POST",
95
+ headers: {
96
+ "content-type": "application/json",
97
+ "x-auth-token": values.secretKey
98
+ },
99
+ body
100
+ });
101
+ const result = await readJson(response);
102
+ if (!response.ok) {
103
+ throw responseError("scaleway", response, errorDetail(result));
104
+ }
105
+ const [email] = result?.emails ?? [];
106
+ return { messageId: stringOf(email, "id") };
107
+ }
108
+ };
109
+ };
110
+ export {
111
+ createScalewayProvider
112
+ };
113
+ //# sourceMappingURL=scaleway.js.map