@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 @@
1
+ {"version":3,"sources":["../src/providers/scaleway.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 checkNoInline,\n readJson,\n responseError,\n stringOf,\n type HttpProviderOptions,\n} from './http.js'\n\n/** Options of `createScalewayProvider()`. */\nexport interface ScalewayProviderOptions extends HttpProviderOptions {\n /** Secret key of an API key with access to Transactional Email. */\n secretKey: string\n /** ID of the Project of the sending domain. */\n projectId: string\n /** Region of the sending domain. Defaults to `fr-par`. */\n region?: string | undefined\n /** Base URL of the API. Defaults to `https://api.scaleway.com`. */\n baseUrl?: string | undefined\n}\n\nconst DEFAULT_BASE_URL = 'https://api.scaleway.com'\nconst DEFAULT_REGION = 'fr-par'\nconst MAX_SUBJECT_LENGTH = 998\n\nconst toAddress = ({ name, address }: Address) =>\n name ? { email: address, name } : { email: address }\n\n/** Checks the limits of the API. */\nconst checkLimits = (message: MailMessage) => {\n checkNoInline('Scaleway', message.attachments)\n if (message.subject.length > MAX_SUBJECT_LENGTH) {\n throw new MailMessageError(\n `Scaleway accepts subjects of at most ${MAX_SUBJECT_LENGTH} characters, got ${message.subject.length}.`\n )\n }\n const empty = Object.entries(message.headers).find(\n ([, value]) => value === ''\n )\n if (empty) {\n throw new MailMessageError(\n `Scaleway does not accept empty header values (${empty[0]}).`\n )\n }\n}\n\n/**\n * Body of the Transactional Email API (v1alpha1), as serialized by the\n * official Scaleway SDK.\n */\nconst toBody = (message: MailMessage, projectId: string) => {\n checkLimits(message)\n const { cc, bcc, replyTo, attachments } = message\n // No Reply-To field: it is an additional header.\n const headers = [\n ...(replyTo.length > 0\n ? [{ key: 'Reply-To', value: replyTo.map(formatAddress).join(', ') }]\n : []),\n ...Object.entries(message.headers).map(([key, value]) => ({ key, value })),\n ]\n return {\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 subject: message.subject,\n text: message.text,\n html: message.html ?? '',\n project_id: projectId,\n ...(attachments.length > 0\n ? {\n attachments: attachments.map(\n ({ filename, content, contentType }) => ({\n name: filename,\n type: contentType ?? 'application/octet-stream',\n content: base64(content),\n })\n ),\n }\n : {}),\n ...(headers.length > 0 ? { additional_headers: headers } : {}),\n }\n}\n\n/** Message of an API error, with its invalid fields. */\nconst errorDetail = (result: unknown) => {\n const message = stringOf(result, 'message')\n const fields = (result as { fields?: unknown } | undefined)?.fields\n if (message === undefined || typeof fields !== 'object' || fields === null) {\n return message\n }\n const details = Object.entries(fields)\n .filter(([, list]) => Array.isArray(list))\n .map(([name, list]) => `${name} (${(list as unknown[]).join(', ')})`)\n return details.length > 0 ? `${message}: ${details.join(', ')}` : message\n}\n\n/**\n * Creates a provider that sends messages with\n * [Scaleway Transactional Email](https://www.scaleway.com/en/transactional-email-tem/),\n * through `fetch`: no dependency.\n *\n * ```ts\n * const provider = createScalewayProvider({\n * secretKey: process.env.SCW_SECRET_KEY!,\n * projectId: process.env.SCW_DEFAULT_PROJECT_ID!,\n * })\n * ```\n */\nexport const createScalewayProvider = (\n options: ScalewayProviderOptions\n): MailProvider => {\n const { values, baseUrl, fetch } = checkHttpOptions(\n 'createScalewayProvider()',\n options,\n ['secretKey', 'projectId'],\n DEFAULT_BASE_URL,\n ['region']\n )\n const { region = DEFAULT_REGION } = values\n if (typeof region !== 'string' || !/^[a-z]{2}-[a-z]{3}$/.test(region)) {\n throw new TypeError(\n 'createScalewayProvider(): region must be a Scaleway region, like fr-par.'\n )\n }\n const url = `${baseUrl}/transactional-email/v1alpha1/regions/${region}/emails`\n\n return {\n name: 'scaleway',\n send: async (message) => {\n const body = JSON.stringify(toBody(message, values.projectId))\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-auth-token': values.secretKey,\n },\n body,\n })\n const result = await readJson(response)\n if (!response.ok) {\n throw responseError('scaleway', response, errorDetail(result))\n }\n // One email per recipient: the first one identifies the message.\n const [email] =\n (result as { emails?: unknown[] } | undefined)?.emails ?? []\n return { messageId: stringOf(email, 'id') }\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,26 @@
1
+ import { MailProvider } from './index.js';
2
+ import { HttpProviderOptions } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Options of `createSendgridProvider()`. */
6
+ interface SendgridProviderOptions extends HttpProviderOptions {
7
+ /** API key, with the Mail Send permission. */
8
+ apiKey: string;
9
+ /**
10
+ * Base URL of the API. Defaults to `https://api.sendgrid.com`; use
11
+ * `https://api.eu.sendgrid.com` for EU regional subusers.
12
+ */
13
+ baseUrl?: string | undefined;
14
+ }
15
+ /**
16
+ * Creates a provider that sends messages with the Mail Send API (v3) of
17
+ * Twilio SendGrid, through `fetch`: no dependency.
18
+ *
19
+ * ```ts
20
+ * const provider = createSendgridProvider({ apiKey: process.env.SENDGRID_API_KEY! })
21
+ * ```
22
+ */
23
+ declare const createSendgridProvider: (options: SendgridProviderOptions) => MailProvider;
24
+
25
+ export { createSendgridProvider };
26
+ export type { SendgridProviderOptions };
@@ -0,0 +1,121 @@
1
+ import {
2
+ checkHttpOptions,
3
+ jsonBody,
4
+ readJson,
5
+ responseError,
6
+ stringOf
7
+ } from "./chunk-FRMKUAF4.js";
8
+ import {
9
+ base64,
10
+ utf8
11
+ } from "./chunk-2ZABFOZQ.js";
12
+ import {
13
+ MailMessageError
14
+ } from "./chunk-QYVN5N3B.js";
15
+
16
+ // src/providers/sendgrid.ts
17
+ var DEFAULT_BASE_URL = "https://api.sendgrid.com";
18
+ var toAddress = ({ name, address }) => name ? { email: address, name } : { email: address };
19
+ var MAX_CATEGORIES = 10;
20
+ var MAX_CATEGORY_LENGTH = 255;
21
+ var MAX_CUSTOM_ARGS_BYTES = 1e4;
22
+ var MAX_REQUEST_BYTES = 3e7;
23
+ var checkTags = ({ tags, metadata }) => {
24
+ if (tags.length > MAX_CATEGORIES) {
25
+ throw new MailMessageError(
26
+ `SendGrid accepts at most ${MAX_CATEGORIES} tags (categories), got ${tags.length}.`
27
+ );
28
+ }
29
+ const long = tags.find((tag) => tag.length > MAX_CATEGORY_LENGTH);
30
+ if (long !== void 0) {
31
+ throw new MailMessageError(
32
+ `SendGrid accepts tags (categories) of at most ${MAX_CATEGORY_LENGTH} characters: "${long.slice(0, 20)}\u2026" has ${long.length}.`
33
+ );
34
+ }
35
+ const size = utf8(JSON.stringify(metadata)).byteLength;
36
+ if (size > MAX_CUSTOM_ARGS_BYTES) {
37
+ throw new MailMessageError(
38
+ `SendGrid accepts at most ${MAX_CUSTOM_ARGS_BYTES} bytes of metadata (custom_args), got ${size}.`
39
+ );
40
+ }
41
+ return { tags, metadata };
42
+ };
43
+ var toBody = (message) => {
44
+ const { to, cc, bcc, replyTo, html, text, attachments, headers } = message;
45
+ const { tags, metadata } = checkTags(message);
46
+ return {
47
+ personalizations: [
48
+ {
49
+ to: to.map(toAddress),
50
+ ...cc.length > 0 ? { cc: cc.map(toAddress) } : {},
51
+ ...bcc.length > 0 ? { bcc: bcc.map(toAddress) } : {},
52
+ ...Object.keys(metadata).length > 0 ? { custom_args: { ...metadata } } : {}
53
+ }
54
+ ],
55
+ from: toAddress(message.from),
56
+ // reply_to and reply_to_list are mutually exclusive.
57
+ ...replyTo.length === 1 ? { reply_to: toAddress(replyTo[0]) } : {},
58
+ ...replyTo.length > 1 ? { reply_to_list: replyTo.map(toAddress) } : {},
59
+ subject: message.subject,
60
+ // text/plain first; empty values are rejected by the API.
61
+ content: [
62
+ ...text === "" ? [] : [{ type: "text/plain", value: text }],
63
+ ...html === void 0 || html === "" ? [] : [{ type: "text/html", value: html }]
64
+ ],
65
+ ...attachments.length > 0 ? {
66
+ attachments: attachments.map(
67
+ ({ filename, content, contentType, contentId }) => ({
68
+ content: base64(content),
69
+ filename,
70
+ ...contentType === void 0 ? {} : { type: contentType },
71
+ ...contentId === void 0 ? { disposition: "attachment" } : { disposition: "inline", content_id: contentId }
72
+ })
73
+ )
74
+ } : {},
75
+ ...Object.keys(headers).length > 0 ? { headers: { ...headers } } : {},
76
+ ...tags.length > 0 ? { categories: [...tags] } : {}
77
+ };
78
+ };
79
+ var errorDetail = (body) => {
80
+ const { errors } = body ?? {};
81
+ if (!Array.isArray(errors)) return void 0;
82
+ const messages = errors.map((error) => stringOf(error, "message")).filter((message) => message !== void 0);
83
+ return messages.length > 0 ? messages.join("; ") : void 0;
84
+ };
85
+ var createSendgridProvider = (options) => {
86
+ const { values, baseUrl, fetch } = checkHttpOptions(
87
+ "createSendgridProvider()",
88
+ options,
89
+ ["apiKey"],
90
+ DEFAULT_BASE_URL
91
+ );
92
+ const { apiKey } = values;
93
+ const url = `${baseUrl}/v3/mail/send`;
94
+ return {
95
+ name: "sendgrid",
96
+ send: async (message) => {
97
+ const response = await fetch(url, {
98
+ method: "POST",
99
+ headers: {
100
+ authorization: `Bearer ${apiKey}`,
101
+ "content-type": "application/json"
102
+ },
103
+ // 30 MB per message, attachments included.
104
+ body: jsonBody("SendGrid", toBody(message), MAX_REQUEST_BYTES)
105
+ });
106
+ if (!response.ok) {
107
+ throw responseError(
108
+ "sendgrid",
109
+ response,
110
+ errorDetail(await readJson(response))
111
+ );
112
+ }
113
+ await response.body?.cancel();
114
+ return { messageId: response.headers.get("x-message-id") ?? void 0 };
115
+ }
116
+ };
117
+ };
118
+ export {
119
+ createSendgridProvider
120
+ };
121
+ //# sourceMappingURL=sendgrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/sendgrid.ts"],"sourcesContent":["import { base64, utf8 } from '../encoding.js'\nimport { MailMessageError } from '../errors.js'\nimport type { MailProvider } from '../mailer.js'\nimport type { Address, 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 `createSendgridProvider()`. */\nexport interface SendgridProviderOptions extends HttpProviderOptions {\n /** API key, with the Mail Send permission. */\n apiKey: string\n /**\n * Base URL of the API. Defaults to `https://api.sendgrid.com`; use\n * `https://api.eu.sendgrid.com` for EU regional subusers.\n */\n baseUrl?: string | undefined\n}\n\nconst DEFAULT_BASE_URL = 'https://api.sendgrid.com'\n\ninterface SendgridAddress {\n email: string\n name?: string\n}\n\nconst toAddress = ({ name, address }: Address): SendgridAddress =>\n name ? { email: address, name } : { email: address }\n\nconst MAX_CATEGORIES = 10\nconst MAX_CATEGORY_LENGTH = 255\nconst MAX_CUSTOM_ARGS_BYTES = 10_000\nconst MAX_REQUEST_BYTES = 30_000_000\n\n/** Limits of the API on categories (tags) and custom_args (metadata). */\nconst checkTags = ({ tags, metadata }: MailMessage) => {\n if (tags.length > MAX_CATEGORIES) {\n throw new MailMessageError(\n `SendGrid accepts at most ${MAX_CATEGORIES} tags (categories), got ${tags.length}.`\n )\n }\n const long = tags.find((tag) => tag.length > MAX_CATEGORY_LENGTH)\n if (long !== undefined) {\n throw new MailMessageError(\n `SendGrid accepts tags (categories) of at most ${MAX_CATEGORY_LENGTH} characters: \"${long.slice(0, 20)}…\" has ${long.length}.`\n )\n }\n const size = utf8(JSON.stringify(metadata)).byteLength\n if (size > MAX_CUSTOM_ARGS_BYTES) {\n throw new MailMessageError(\n `SendGrid accepts at most ${MAX_CUSTOM_ARGS_BYTES} bytes of metadata (custom_args), got ${size}.`\n )\n }\n return { tags, metadata }\n}\n\n/**\n * Body of the Mail Send API (v3), see\n * https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send\n */\nconst toBody = (message: MailMessage) => {\n const { to, cc, bcc, replyTo, html, text, attachments, headers } = message\n const { tags, metadata } = checkTags(message)\n return {\n personalizations: [\n {\n to: to.map(toAddress),\n ...(cc.length > 0 ? { cc: cc.map(toAddress) } : {}),\n ...(bcc.length > 0 ? { bcc: bcc.map(toAddress) } : {}),\n ...(Object.keys(metadata).length > 0\n ? { custom_args: { ...metadata } }\n : {}),\n },\n ],\n from: toAddress(message.from),\n // reply_to and reply_to_list are mutually exclusive.\n ...(replyTo.length === 1 ? { reply_to: toAddress(replyTo[0]!) } : {}),\n ...(replyTo.length > 1 ? { reply_to_list: replyTo.map(toAddress) } : {}),\n subject: message.subject,\n // text/plain first; empty values are rejected by the API.\n content: [\n ...(text === '' ? [] : [{ type: 'text/plain', value: text }]),\n ...(html === undefined || html === ''\n ? []\n : [{ type: 'text/html', value: html }]),\n ],\n ...(attachments.length > 0\n ? {\n attachments: attachments.map(\n ({ filename, content, contentType, contentId }) => ({\n content: base64(content),\n filename,\n ...(contentType === undefined ? {} : { type: contentType }),\n ...(contentId === undefined\n ? { disposition: 'attachment' }\n : { disposition: 'inline', content_id: contentId }),\n })\n ),\n }\n : {}),\n ...(Object.keys(headers).length > 0 ? { headers: { ...headers } } : {}),\n ...(tags.length > 0 ? { categories: [...tags] } : {}),\n }\n}\n\n/** Error messages of an API response (`errors[].message`), if any. */\nconst errorDetail = (body: unknown) => {\n const { errors } = (body ?? {}) as { errors?: unknown }\n if (!Array.isArray(errors)) return undefined\n const messages = errors\n .map((error) => stringOf(error, 'message'))\n .filter((message) => message !== undefined)\n return messages.length > 0 ? messages.join('; ') : undefined\n}\n\n/**\n * Creates a provider that sends messages with the Mail Send API (v3) of\n * Twilio SendGrid, through `fetch`: no dependency.\n *\n * ```ts\n * const provider = createSendgridProvider({ apiKey: process.env.SENDGRID_API_KEY! })\n * ```\n */\nexport const createSendgridProvider = (\n options: SendgridProviderOptions\n): MailProvider => {\n const { values, baseUrl, fetch } = checkHttpOptions(\n 'createSendgridProvider()',\n options,\n ['apiKey'],\n DEFAULT_BASE_URL\n )\n const { apiKey } = values\n const url = `${baseUrl}/v3/mail/send`\n\n return {\n name: 'sendgrid',\n send: async (message) => {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n // 30 MB per message, attachments included.\n body: jsonBody('SendGrid', toBody(message), MAX_REQUEST_BYTES),\n })\n if (!response.ok) {\n throw responseError(\n 'sendgrid',\n response,\n errorDetail(await readJson(response))\n )\n }\n // 202 Accepted, without body.\n await response.body?.cancel()\n return { messageId: response.headers.get('x-message-id') ?? undefined }\n },\n }\n}\n"],"mappings":"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","names":[]}
package/dist/smtp.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { SendMailOptions, SMTPTransportOptions, SMTPPoolOptions } from 'nodemailer';
2
+ import { MailProvider } from './index.js';
3
+ import 'zod';
4
+
5
+ /** A nodemailer transporter, or any object with the same methods. */
6
+ interface NodemailerTransporter {
7
+ sendMail(options: SendMailOptions): Promise<{
8
+ messageId?: unknown;
9
+ }>;
10
+ close?(): void;
11
+ }
12
+ /** Provider based on nodemailer: SMTP, or another nodemailer transport. */
13
+ interface NodemailerProvider extends MailProvider {
14
+ /** Closes the connections of the transporter (`pool: true`). */
15
+ close(): void;
16
+ }
17
+ /**
18
+ * Options of `createSmtpProvider()`: the SMTP options of nodemailer
19
+ * (`host`, `port`, `secure`, `auth`, `pool`…), or a connection URL like
20
+ * `smtps://user:password@smtp.example.com`.
21
+ */
22
+ type SmtpProviderOptions = SMTPTransportOptions | SMTPPoolOptions | string;
23
+ /**
24
+ * Creates a provider from a nodemailer transporter: SMTP, sendmail,
25
+ * Amazon SES through nodemailer, or a stream transport for tests.
26
+ */
27
+ declare const createNodemailerProvider: (transporter: NodemailerTransporter, name?: string) => NodemailerProvider;
28
+ /**
29
+ * Creates a provider that sends messages to an SMTP server, through
30
+ * nodemailer (peer dependency). Also works with the SMTP interface of
31
+ * Amazon SES, Postmark, Mailgun, Brevo, Mailjet…
32
+ *
33
+ * ```ts
34
+ * const provider = createSmtpProvider({
35
+ * host: 'smtp.example.com',
36
+ * port: 465,
37
+ * secure: true,
38
+ * auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD },
39
+ * })
40
+ * ```
41
+ */
42
+ declare const createSmtpProvider: (options: SmtpProviderOptions) => NodemailerProvider;
43
+
44
+ export { createNodemailerProvider, createSmtpProvider };
45
+ export type { NodemailerProvider, NodemailerTransporter, SmtpProviderOptions };
package/dist/smtp.js ADDED
@@ -0,0 +1,61 @@
1
+ // src/providers/smtp.ts
2
+ import nodemailer from "nodemailer";
3
+ var toAddress = ({ name, address }) => name ? { name, address } : address;
4
+ var toAttachment = ({
5
+ filename,
6
+ content,
7
+ contentType,
8
+ contentId
9
+ }) => ({
10
+ filename,
11
+ // nodemailer takes Buffers, not plain Uint8Arrays (no copy).
12
+ content: typeof content === "string" ? content : Buffer.from(content.buffer, content.byteOffset, content.byteLength),
13
+ ...contentType === void 0 ? {} : { contentType },
14
+ // nodemailer defaults to `attachment`.
15
+ ...contentId === void 0 ? {} : { cid: contentId, contentDisposition: "inline" }
16
+ });
17
+ var toNodemailerMessage = (message) => ({
18
+ from: toAddress(message.from),
19
+ to: message.to.map(toAddress),
20
+ cc: message.cc.map(toAddress),
21
+ bcc: message.bcc.map(toAddress),
22
+ replyTo: message.replyTo.map(toAddress),
23
+ subject: message.subject,
24
+ text: message.text,
25
+ html: message.html,
26
+ attachments: message.attachments.map(toAttachment),
27
+ headers: { ...message.headers }
28
+ });
29
+ var createNodemailerProvider = (transporter, name = "nodemailer") => {
30
+ if (typeof transporter?.sendMail !== "function") {
31
+ throw new TypeError(
32
+ "createNodemailerProvider(): expected a nodemailer transporter."
33
+ );
34
+ }
35
+ return {
36
+ name,
37
+ send: async (message) => {
38
+ const { messageId } = await transporter.sendMail(
39
+ toNodemailerMessage(message)
40
+ );
41
+ return { messageId };
42
+ },
43
+ close: () => transporter.close?.()
44
+ };
45
+ };
46
+ var createSmtpProvider = (options) => {
47
+ if (typeof options !== "string" && (typeof options !== "object" || options === null)) {
48
+ throw new TypeError(
49
+ "createSmtpProvider(): expected SMTP options or a connection URL."
50
+ );
51
+ }
52
+ const transporter = nodemailer.createTransport(
53
+ options
54
+ );
55
+ return createNodemailerProvider(transporter, "smtp");
56
+ };
57
+ export {
58
+ createNodemailerProvider,
59
+ createSmtpProvider
60
+ };
61
+ //# sourceMappingURL=smtp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/smtp.ts"],"sourcesContent":["// nodemailer runs on Node.js only: Buffer is available.\n/// <reference types=\"node\" />\nimport nodemailer, {\n type SendMailOptions,\n type SMTPPoolOptions,\n type SMTPTransportOptions,\n type Transporter,\n} from 'nodemailer'\nimport type { MailProvider } from '../mailer.js'\nimport type { Address, MailAttachment, MailMessage } from '../message.js'\n\n/** A nodemailer transporter, or any object with the same methods. */\nexport interface NodemailerTransporter {\n sendMail(options: SendMailOptions): Promise<{ messageId?: unknown }>\n close?(): void\n}\n\n/** Provider based on nodemailer: SMTP, or another nodemailer transport. */\nexport interface NodemailerProvider extends MailProvider {\n /** Closes the connections of the transporter (`pool: true`). */\n close(): void\n}\n\n/**\n * Options of `createSmtpProvider()`: the SMTP options of nodemailer\n * (`host`, `port`, `secure`, `auth`, `pool`…), or a connection URL like\n * `smtps://user:password@smtp.example.com`.\n */\nexport type SmtpProviderOptions =\n SMTPTransportOptions | SMTPPoolOptions | string\n\nconst toAddress = ({ name, address }: Address) =>\n name ? { name, address } : address\n\nconst toAttachment = ({\n filename,\n content,\n contentType,\n contentId,\n}: MailAttachment) => ({\n filename,\n // nodemailer takes Buffers, not plain Uint8Arrays (no copy).\n content:\n typeof content === 'string'\n ? content\n : Buffer.from(content.buffer, content.byteOffset, content.byteLength),\n ...(contentType === undefined ? {} : { contentType }),\n // nodemailer defaults to `attachment`.\n ...(contentId === undefined\n ? {}\n : { cid: contentId, contentDisposition: 'inline' as const }),\n})\n\n/** nodemailer options of a message. */\nconst toNodemailerMessage = (message: MailMessage): SendMailOptions => ({\n from: toAddress(message.from),\n to: message.to.map(toAddress),\n cc: message.cc.map(toAddress),\n bcc: message.bcc.map(toAddress),\n replyTo: message.replyTo.map(toAddress),\n subject: message.subject,\n text: message.text,\n html: message.html,\n attachments: message.attachments.map(toAttachment),\n headers: { ...message.headers },\n})\n\n/**\n * Creates a provider from a nodemailer transporter: SMTP, sendmail,\n * Amazon SES through nodemailer, or a stream transport for tests.\n */\nexport const createNodemailerProvider = (\n transporter: NodemailerTransporter,\n name = 'nodemailer'\n): NodemailerProvider => {\n if (typeof transporter?.sendMail !== 'function') {\n throw new TypeError(\n 'createNodemailerProvider(): expected a nodemailer transporter.'\n )\n }\n return {\n name,\n send: async (message) => {\n const { messageId } = await transporter.sendMail(\n toNodemailerMessage(message)\n )\n // Checked by the mailer.\n return { messageId: messageId as string | undefined }\n },\n close: () => transporter.close?.(),\n }\n}\n\n/**\n * Creates a provider that sends messages to an SMTP server, through\n * nodemailer (peer dependency). Also works with the SMTP interface of\n * Amazon SES, Postmark, Mailgun, Brevo, Mailjet…\n *\n * ```ts\n * const provider = createSmtpProvider({\n * host: 'smtp.example.com',\n * port: 465,\n * secure: true,\n * auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD },\n * })\n * ```\n */\nexport const createSmtpProvider = (\n options: SmtpProviderOptions\n): NodemailerProvider => {\n if (\n typeof options !== 'string' &&\n (typeof options !== 'object' || options === null)\n ) {\n throw new TypeError(\n 'createSmtpProvider(): expected SMTP options or a connection URL.'\n )\n }\n const transporter: Transporter = nodemailer.createTransport(\n options as SMTPTransportOptions\n )\n return createNodemailerProvider(transporter, 'smtp')\n}\n"],"mappings":";AAEA,OAAO,gBAKA;AAwBP,IAAM,YAAY,CAAC,EAAE,MAAM,QAAQ,MACjC,OAAO,EAAE,MAAM,QAAQ,IAAI;AAE7B,IAAM,eAAe,CAAC;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,OAAuB;AAAA,EACrB;AAAA;AAAA,EAEA,SACE,OAAO,YAAY,WACf,UACA,OAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;AAAA,EACxE,GAAI,gBAAgB,SAAY,CAAC,IAAI,EAAE,YAAY;AAAA;AAAA,EAEnD,GAAI,cAAc,SACd,CAAC,IACD,EAAE,KAAK,WAAW,oBAAoB,SAAkB;AAC9D;AAGA,IAAM,sBAAsB,CAAC,aAA2C;AAAA,EACtE,MAAM,UAAU,QAAQ,IAAI;AAAA,EAC5B,IAAI,QAAQ,GAAG,IAAI,SAAS;AAAA,EAC5B,IAAI,QAAQ,GAAG,IAAI,SAAS;AAAA,EAC5B,KAAK,QAAQ,IAAI,IAAI,SAAS;AAAA,EAC9B,SAAS,QAAQ,QAAQ,IAAI,SAAS;AAAA,EACtC,SAAS,QAAQ;AAAA,EACjB,MAAM,QAAQ;AAAA,EACd,MAAM,QAAQ;AAAA,EACd,aAAa,QAAQ,YAAY,IAAI,YAAY;AAAA,EACjD,SAAS,EAAE,GAAG,QAAQ,QAAQ;AAChC;AAMO,IAAM,2BAA2B,CACtC,aACA,OAAO,iBACgB;AACvB,MAAI,OAAO,aAAa,aAAa,YAAY;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,OAAO,YAAY;AACvB,YAAM,EAAE,UAAU,IAAI,MAAM,YAAY;AAAA,QACtC,oBAAoB,OAAO;AAAA,MAC7B;AAEA,aAAO,EAAE,UAA2C;AAAA,IACtD;AAAA,IACA,OAAO,MAAM,YAAY,QAAQ;AAAA,EACnC;AACF;AAgBO,IAAM,qBAAqB,CAChC,YACuB;AACvB,MACE,OAAO,YAAY,aAClB,OAAO,YAAY,YAAY,YAAY,OAC5C;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAA2B,WAAW;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,yBAAyB,aAAa,MAAM;AACrD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,142 @@
1
1
  {
2
2
  "name": "@dotpress/mailer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.0.0-alpha.0",
4
+ "description": "Typed, multilingual email templates (MJML or HTML) and interchangeable providers",
5
+ "author": "Renaud Guyon",
6
+ "license": "MIT",
7
+ "homepage": "https://dotpress.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/dotpressjs/dotpress.git",
11
+ "directory": "packages/mailer"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/dotpressjs/dotpress/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./plugin": {
29
+ "types": "./dist/plugin.d.ts",
30
+ "default": "./dist/plugin.js"
31
+ },
32
+ "./smtp": {
33
+ "types": "./dist/smtp.d.ts",
34
+ "default": "./dist/smtp.js"
35
+ },
36
+ "./aws-ses": {
37
+ "types": "./dist/aws-ses.d.ts",
38
+ "default": "./dist/aws-ses.js"
39
+ },
40
+ "./resend": {
41
+ "types": "./dist/resend.d.ts",
42
+ "default": "./dist/resend.js"
43
+ },
44
+ "./sendgrid": {
45
+ "types": "./dist/sendgrid.d.ts",
46
+ "default": "./dist/sendgrid.js"
47
+ },
48
+ "./postmark": {
49
+ "types": "./dist/postmark.d.ts",
50
+ "default": "./dist/postmark.js"
51
+ },
52
+ "./brevo": {
53
+ "types": "./dist/brevo.d.ts",
54
+ "default": "./dist/brevo.js"
55
+ },
56
+ "./mailjet": {
57
+ "types": "./dist/mailjet.d.ts",
58
+ "default": "./dist/mailjet.js"
59
+ },
60
+ "./mailgun": {
61
+ "types": "./dist/mailgun.d.ts",
62
+ "default": "./dist/mailgun.js"
63
+ },
64
+ "./scaleway": {
65
+ "types": "./dist/scaleway.d.ts",
66
+ "default": "./dist/scaleway.js"
67
+ }
68
+ },
69
+ "engines": {
70
+ "node": ">=22.12"
71
+ },
72
+ "peerDependencies": {
73
+ "@aws-sdk/client-sesv2": "^3.782.0",
74
+ "@dotpress/core": "^2.0.0-alpha.0",
75
+ "mjml": "^5.0.0",
76
+ "nodemailer": "^10.0.0",
77
+ "resend": "^6.0.0",
78
+ "zod": "^4.6.0"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "@aws-sdk/client-sesv2": {
82
+ "optional": true
83
+ },
84
+ "@dotpress/core": {
85
+ "optional": true
86
+ },
87
+ "mjml": {
88
+ "optional": true
89
+ },
90
+ "nodemailer": {
91
+ "optional": true
92
+ },
93
+ "resend": {
94
+ "optional": true
95
+ }
96
+ },
97
+ "dependencies": {
98
+ "html-to-text": "^10.0.1",
99
+ "mustache": "^4.2.0"
100
+ },
101
+ "devDependencies": {
102
+ "@aws-sdk/client-sesv2": "^3.1164.0",
103
+ "@dotpress/core": "2.0.0-alpha.0",
104
+ "@dotpress/testing": "2.0.0-alpha.0",
105
+ "@types/html-to-text": "^9.0.4",
106
+ "@types/mjml": "^5.4.2",
107
+ "@types/mustache": "^4.2.6",
108
+ "mjml": "^5.4.1",
109
+ "nodemailer": "^10.4.0",
110
+ "resend": "^6.37.0",
111
+ "smtp-server": "^3.21.3",
112
+ "zod": "^4.6.7"
113
+ },
114
+ "keywords": [
115
+ "dotpress",
116
+ "email",
117
+ "mailer",
118
+ "plugin",
119
+ "mjml",
120
+ "i18n",
121
+ "templates",
122
+ "typescript",
123
+ "smtp",
124
+ "nodemailer",
125
+ "aws-ses",
126
+ "aws",
127
+ "resend",
128
+ "sendgrid",
129
+ "postmark",
130
+ "brevo",
131
+ "mailjet",
132
+ "mailgun",
133
+ "scaleway"
134
+ ],
135
+ "scripts": {
136
+ "build": "tsup",
137
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
138
+ "lint": "eslint src test",
139
+ "test": "vitest run",
140
+ "test:watch": "vitest watch"
141
+ }
6
142
  }