@visulima/email 4.0.0 → 4.2.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 (178) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +74 -1
  3. package/dist/crypto/index.d.ts +2 -2
  4. package/dist/deliverability/dmarc-report.js +1 -1
  5. package/dist/deliverability/index.d.ts +1 -1
  6. package/dist/inbound/index.d.ts +1 -1
  7. package/dist/index.d.ts +11 -5
  8. package/dist/index.js +1 -1
  9. package/dist/middleware/index.d.ts +3 -3
  10. package/dist/middleware/index.js +1 -1
  11. package/dist/packem_chunks/index.js +5 -5
  12. package/dist/packem_shared/{DraftMailMessage-B0gwf8n_.js → DraftMailMessage-zkKTOxQ8.js} +1 -1
  13. package/dist/packem_shared/EmailError-SXEFQEyA.js +1 -0
  14. package/dist/packem_shared/Mail-DtpXUSIl.js +1 -0
  15. package/dist/packem_shared/MailMessage-Aw0vVAh-.js +1 -0
  16. package/dist/packem_shared/{RequiredOptionError-ctDEEB1s.js → RequiredOptionError-C2wWqmiE.js} +1 -1
  17. package/dist/packem_shared/{ahaSendProvider-D95-pwFh.js → ahaSendProvider-Ci_a_O8d.js} +1 -1
  18. package/dist/packem_shared/{attachment-processor-BJFDS343.js → attachment-processor-AB1m-n9T.js} +1 -1
  19. package/dist/packem_shared/{autoSendProvider-yxUx3sEj.js → autoSendProvider-DG3pNJML.js} +1 -1
  20. package/dist/packem_shared/{awsSesProvider-DnZo9X_g.js → awsSesProvider-C4b5kuL4.js} +1 -1
  21. package/dist/packem_shared/{azureProvider-D6Z4c1yT.js → azureProvider-CEnz3uy_.js} +1 -1
  22. package/dist/packem_shared/{brevoProvider-D1yTSs0l.js → brevoProvider-BsbInXjw.js} +1 -1
  23. package/dist/packem_shared/{build-mime-message-BCFKPqaW.js → build-mime-message-D45WdgfP.js} +1 -1
  24. package/dist/packem_shared/{circuitBreakerMiddleware-DcBN7K6H.js → circuitBreakerMiddleware-Cm0m75qi.js} +1 -1
  25. package/dist/packem_shared/{cloudflareEmailProvider-DZbs35NL.js → cloudflareEmailProvider-CnpH5R5p.js} +1 -1
  26. package/dist/packem_shared/{createTestEmail-gr512snl.js → createTestEmail-B2Fz-Ifb.js} +1 -1
  27. package/dist/packem_shared/createWorker-ChrQvLUF.js +1 -0
  28. package/dist/packem_shared/extractLinks-BwE1SjNF.js +1 -0
  29. package/dist/packem_shared/{failoverProvider-IED1M0M5.js → failoverProvider-DsBqMOfK.js} +1 -1
  30. package/dist/packem_shared/fileProvider-BnDeaSqA.js +1 -0
  31. package/dist/packem_shared/{httpProvider-CpdWJoRq.js → httpProvider-BuA00407.js} +1 -1
  32. package/dist/packem_shared/{infobipProvider-DTqFu5pM.js → infobipProvider-DVUg9jKC.js} +1 -1
  33. package/dist/packem_shared/{isValidSigningCertUrl-CK1t2a7x.js → isValidSigningCertUrl-gnYr3oyD.js} +1 -1
  34. package/dist/packem_shared/{loopsProvider-PiE2HxoW.js → loopsProvider-BJUNQ2rR.js} +1 -1
  35. package/dist/packem_shared/{mail.d-BdyyyQO5.d.ts → mail.d-2u-30lWz.d.ts} +12 -4
  36. package/dist/packem_shared/{mailCrabProvider-BSapw_mS.js → mailCrabProvider-CFKRofS5.js} +1 -1
  37. package/dist/packem_shared/{mailPaceProvider-BugIutTw.js → mailPaceProvider-DBQd6NYt.js} +1 -1
  38. package/dist/packem_shared/{mailchannelsProvider-Dn24mI8D.js → mailchannelsProvider-CPVW3wlH.js} +1 -1
  39. package/dist/packem_shared/{mailerSendProvider-B6X4Lhsf.js → mailerSendProvider-B2H6BtoV.js} +1 -1
  40. package/dist/packem_shared/{mailgunProvider-4sMtcQcW.js → mailgunProvider-JRVH70PB.js} +1 -1
  41. package/dist/packem_shared/{mailjetProvider-DvrHwipw.js → mailjetProvider-BzBovF5M.js} +1 -1
  42. package/dist/packem_shared/{mailomatProvider-CrkNQGfL.js → mailomatProvider-x6IHKuWU.js} +1 -1
  43. package/dist/packem_shared/{mailtrapProvider-DTgvCUrs.js → mailtrapProvider-CJTJoDcV.js} +1 -1
  44. package/dist/packem_shared/make-request-mszP240P.js +1 -0
  45. package/dist/packem_shared/{mandrillProvider-BaK0GEj7.js → mandrillProvider-BcztgKkr.js} +1 -1
  46. package/dist/packem_shared/{mockProvider-CTxxbT0d.js → mockProvider-DVtl5Jjy.js} +1 -1
  47. package/dist/packem_shared/{netcoreProvider-BZJ0noQ2.js → netcoreProvider-YWEpa-wi.js} +1 -1
  48. package/dist/packem_shared/{nodemailerProvider-DqJh8ghN.js → nodemailerProvider-WNNtLE2s.js} +1 -1
  49. package/dist/packem_shared/{opentelemetryProvider-s0gvh05u.js → opentelemetryProvider-ddbILMeS.js} +1 -1
  50. package/dist/packem_shared/{outlook365Provider-DQUSFy2d.js → outlook365Provider-D5eETMZq.js} +1 -1
  51. package/dist/packem_shared/{plunkProvider-WMi1yPya.js → plunkProvider-TrBBoX7_.js} +1 -1
  52. package/dist/packem_shared/{postalProvider-DpBFGpoX.js → postalProvider-CU53hX0R.js} +1 -1
  53. package/dist/packem_shared/{postmarkProvider-ClUaHPvw.js → postmarkProvider-DEYQp8Gv.js} +1 -1
  54. package/dist/packem_shared/{provider-base-BEz00m9R.js → provider-base-G22b4a87.js} +1 -1
  55. package/dist/packem_shared/{provider.d-Cx5YQJdP.d.ts → provider.d-BLLbEuQ8.d.ts} +1 -1
  56. package/dist/packem_shared/resendProvider-BaIZCvph.js +1 -0
  57. package/dist/packem_shared/{retry-aghj7y8m.js → retry-ByPJFAtq.js} +1 -1
  58. package/dist/packem_shared/{roundRobinProvider-Dr1DdT2L.js → roundRobinProvider-CfuNY7We.js} +1 -1
  59. package/dist/packem_shared/{scalewayProvider-BhbDUEah.js → scalewayProvider-x6S2ZSPd.js} +1 -1
  60. package/dist/packem_shared/{sendGridProvider-DBZHhxd6.js → sendGridProvider-8Po3P-ia.js} +1 -1
  61. package/dist/packem_shared/{smtpProvider-CjcOEiAh.js → smtpProvider-BPamDNuv.js} +1 -1
  62. package/dist/packem_shared/{sparkpostProvider-B19lU9D5.js → sparkpostProvider-BVu1KeNz.js} +1 -1
  63. package/dist/packem_shared/{sweegoProvider-H8KWGDff.js → sweegoProvider-DHISjS9s.js} +1 -1
  64. package/dist/packem_shared/{types.d-Cb5fls_X.d.ts → types.d-A1Eea-p_.d.ts} +1 -1
  65. package/dist/packem_shared/{types.d-kHO4kX9q.d.ts → types.d-B5iH_mgb.d.ts} +1 -1
  66. package/dist/packem_shared/{types.d-GKqPNk0I.d.ts → types.d-Chhzb-48.d.ts} +1 -1
  67. package/dist/packem_shared/{types.d-B9295M4_.d.ts → types.d-CyiC9uRC.d.ts} +10 -0
  68. package/dist/packem_shared/{types.d-C5geVKes.d.ts → types.d-V_yuAOrx.d.ts} +1 -1
  69. package/dist/packem_shared/{verifySendGridWebhook-csuRasH3.js → verifySendGridWebhook-0_3qKQA1.js} +1 -1
  70. package/dist/packem_shared/{withSuppression-B0eZ2xNQ.js → withSuppression-COCHL9Zx.js} +1 -1
  71. package/dist/packem_shared/{zeptomailProvider-L7nkdn0L.js → zeptomailProvider-IhCIl_2a.js} +1 -1
  72. package/dist/providers/ahasend/index.d.ts +3 -3
  73. package/dist/providers/ahasend/index.js +1 -1
  74. package/dist/providers/autosend/index.d.ts +3 -3
  75. package/dist/providers/autosend/index.js +1 -1
  76. package/dist/providers/aws-ses/index.d.ts +3 -3
  77. package/dist/providers/aws-ses/index.js +1 -1
  78. package/dist/providers/azure/index.d.ts +3 -3
  79. package/dist/providers/azure/index.js +1 -1
  80. package/dist/providers/brevo/index.d.ts +3 -3
  81. package/dist/providers/brevo/index.js +1 -1
  82. package/dist/providers/cloudflare-email/index.d.ts +4 -4
  83. package/dist/providers/cloudflare-email/index.js +1 -1
  84. package/dist/providers/failover/index.d.ts +3 -3
  85. package/dist/providers/failover/index.js +1 -1
  86. package/dist/providers/file/index.d.ts +23 -0
  87. package/dist/providers/file/index.js +1 -0
  88. package/dist/providers/http/index.d.ts +3 -3
  89. package/dist/providers/http/index.js +1 -1
  90. package/dist/providers/infobip/index.d.ts +3 -3
  91. package/dist/providers/infobip/index.js +1 -1
  92. package/dist/providers/loops/index.d.ts +3 -3
  93. package/dist/providers/loops/index.js +1 -1
  94. package/dist/providers/mailchannels/index.d.ts +3 -3
  95. package/dist/providers/mailchannels/index.js +1 -1
  96. package/dist/providers/mailcrab/index.d.ts +3 -3
  97. package/dist/providers/mailcrab/index.js +1 -1
  98. package/dist/providers/mailersend/index.d.ts +3 -3
  99. package/dist/providers/mailersend/index.js +1 -1
  100. package/dist/providers/mailgun/index.d.ts +3 -3
  101. package/dist/providers/mailgun/index.js +1 -1
  102. package/dist/providers/mailjet/index.d.ts +3 -3
  103. package/dist/providers/mailjet/index.js +1 -1
  104. package/dist/providers/mailomat/index.d.ts +3 -3
  105. package/dist/providers/mailomat/index.js +1 -1
  106. package/dist/providers/mailpace/index.d.ts +3 -3
  107. package/dist/providers/mailpace/index.js +1 -1
  108. package/dist/providers/mailtrap/index.d.ts +3 -3
  109. package/dist/providers/mailtrap/index.js +1 -1
  110. package/dist/providers/mandrill/index.d.ts +3 -3
  111. package/dist/providers/mandrill/index.js +1 -1
  112. package/dist/providers/mock/index.d.ts +5 -5
  113. package/dist/providers/mock/index.js +1 -1
  114. package/dist/providers/netcore/index.d.ts +3 -3
  115. package/dist/providers/netcore/index.js +1 -1
  116. package/dist/providers/nodemailer/index.d.ts +3 -3
  117. package/dist/providers/nodemailer/index.js +1 -1
  118. package/dist/providers/opentelemetry/index.d.ts +2 -2
  119. package/dist/providers/opentelemetry/index.js +1 -1
  120. package/dist/providers/outlook365/index.d.ts +3 -3
  121. package/dist/providers/outlook365/index.js +1 -1
  122. package/dist/providers/plunk/index.d.ts +3 -3
  123. package/dist/providers/plunk/index.js +1 -1
  124. package/dist/providers/postal/index.d.ts +3 -3
  125. package/dist/providers/postal/index.js +1 -1
  126. package/dist/providers/postmark/index.d.ts +3 -3
  127. package/dist/providers/postmark/index.js +1 -1
  128. package/dist/providers/resend/index.d.ts +3 -3
  129. package/dist/providers/resend/index.js +1 -1
  130. package/dist/providers/roundrobin/index.d.ts +3 -3
  131. package/dist/providers/roundrobin/index.js +1 -1
  132. package/dist/providers/scaleway/index.d.ts +3 -3
  133. package/dist/providers/scaleway/index.js +1 -1
  134. package/dist/providers/sendgrid/index.d.ts +3 -3
  135. package/dist/providers/sendgrid/index.js +1 -1
  136. package/dist/providers/smtp/index.d.ts +4 -4
  137. package/dist/providers/smtp/index.js +1 -1
  138. package/dist/providers/sparkpost/index.d.ts +3 -3
  139. package/dist/providers/sparkpost/index.js +1 -1
  140. package/dist/providers/sweego/index.d.ts +3 -3
  141. package/dist/providers/sweego/index.js +1 -1
  142. package/dist/providers/zeptomail/index.d.ts +3 -3
  143. package/dist/providers/zeptomail/index.js +1 -1
  144. package/dist/queue/bullmq-queue.d.ts +2 -2
  145. package/dist/queue/index.d.ts +4 -3
  146. package/dist/queue/index.js +1 -1
  147. package/dist/queue/pg-boss-queue.d.ts +2 -2
  148. package/dist/queue/sqs-queue.d.ts +2 -2
  149. package/dist/queue/unstorage-queue.d.ts +2 -2
  150. package/dist/render/css-inline.js +1 -1
  151. package/dist/template-engines/handlebars.js +1 -1
  152. package/dist/template-engines/html-to-text.js +1 -1
  153. package/dist/template-engines/i18n.js +1 -1
  154. package/dist/template-engines/jsx-email.js +1 -1
  155. package/dist/template-engines/liquid.js +1 -1
  156. package/dist/template-engines/locale-files.d.ts +66 -0
  157. package/dist/template-engines/locale-files.js +1 -0
  158. package/dist/template-engines/mjml.js +1 -1
  159. package/dist/template-engines/react-email.js +1 -1
  160. package/dist/template-engines/vue-email.js +1 -1
  161. package/dist/test/index.d.ts +24 -7
  162. package/dist/test/index.js +1 -1
  163. package/dist/utils/format-email-address.d.ts +1 -1
  164. package/dist/utils/format-email-address.js +1 -1
  165. package/dist/utils/parse-address.d.ts +1 -1
  166. package/dist/utils/parse-eml.d.ts +1 -1
  167. package/dist/utils/result.d.ts +1 -1
  168. package/dist/utils/result.js +1 -1
  169. package/dist/utils/validation/check-feature-support.d.ts +1 -1
  170. package/dist/utils/validation/check-feature-support.js +1 -1
  171. package/dist/webhooks/index.js +1 -1
  172. package/package.json +9 -1
  173. package/dist/packem_shared/EmailError-BIaHK1Uw.js +0 -1
  174. package/dist/packem_shared/Mail-DmYNQ0KK.js +0 -1
  175. package/dist/packem_shared/MailMessage-DmmaNHHc.js +0 -1
  176. package/dist/packem_shared/createWorker-ChuTgETP.js +0 -1
  177. package/dist/packem_shared/make-request-Cyz5_yu8.js +0 -1
  178. package/dist/packem_shared/resendProvider-DtaB7sk0.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## @visulima/email [4.2.0](https://github.com/visulima/visulima/compare/@visulima/email@4.1.0...@visulima/email@4.2.0) (2026-10-23)
2
+
3
+ ### Features
4
+
5
+ * **email:** add idempotency key for resend ([#982](https://github.com/visulima/visulima/issues/982)) ([53cfa4d](https://github.com/visulima/visulima/commit/53cfa4da67ee38dfdcaf6cd92f518b09adfeaf19))
6
+
7
+ ## @visulima/email [4.1.0](https://github.com/visulima/visulima/compare/@visulima/email@4.0.0...@visulima/email@4.1.0) (2026-10-23)
8
+
9
+ ### Features
10
+
11
+ * **email:** add extractLinks test helper ([#984](https://github.com/visulima/visulima/issues/984)) ([183c91f](https://github.com/visulima/visulima/commit/183c91f1e192b364773515da2ce8dbe8deafaa6d))
12
+ * **email:** add file provider for .eml output ([#983](https://github.com/visulima/visulima/issues/983)) ([9768708](https://github.com/visulima/visulima/commit/97687081ff5ac190b0d63a4f0f7442cc64ab0f55))
13
+ * **email:** dead-letter permanent failures early ([#985](https://github.com/visulima/visulima/issues/985)) ([b42d51d](https://github.com/visulima/visulima/commit/b42d51d61e4ab1e4277e4d85e28e877912920651))
14
+ * **email:** load locale-specific template files ([#986](https://github.com/visulima/visulima/issues/986)) ([cfdc461](https://github.com/visulima/visulima/commit/cfdc461091566a46168122096dade10a37477c59))
15
+
1
16
  ## @visulima/email [4.0.0](https://github.com/visulima/visulima/compare/@visulima/email@3.0.28...@visulima/email@4.0.0) (2026-10-15)
2
17
 
3
18
  ### ⚠ BREAKING CHANGES
package/README.md CHANGED
@@ -154,6 +154,23 @@ import { checkFeatureSupport } from "@visulima/email";
154
154
  const { supported, violations } = checkFeatureSupport(message, provider.features);
155
155
  ```
156
156
 
157
+ ### Idempotency Keys
158
+
159
+ Pass an `idempotencyKey` to tell the provider that a repeated send is the same message, so it is not delivered twice. The key is set on the message builder or on `EmailOptions`:
160
+
161
+ ```ts
162
+ await mail.send(
163
+ new MailMessage()
164
+ .from("orders@example.com")
165
+ .to("user@example.com")
166
+ .subject("Order confirmed")
167
+ .text("Thanks for your order")
168
+ .idempotencyKey(`order-${orderId}-confirmation`),
169
+ );
170
+ ```
171
+
172
+ The key is sent as an `Idempotency-Key` HTTP header and never appears in the message headers or the MIME body. Only providers that declare `features.idempotency` (currently Resend) use it; other providers ignore the key. Keys must be 1–256 visible ASCII characters. Resend keeps a key for 24 hours and requires a retry to carry the same payload; a retry with a different payload is rejected, and a retry after 24 hours can deliver the email again. The key is therefore protection against accidental double sends within that window, not a permanent guarantee. `sendBatch` does not apply a batch-level key to each recipient, because that would make every recipient look like a repeat of the first.
173
+
157
174
  ### Creating Draft Emails
158
175
 
159
176
  You can create draft emails in EML (RFC 822) format without sending them. The `draft()` method returns the email as an EML string with an `X-Unsent: 1` header automatically added:
@@ -1122,6 +1139,27 @@ mock.reset();
1122
1139
  - `waitForMessage(predicate, timeout)` - Wait for matching message
1123
1140
  - `reset()` - Reset to initial state
1124
1141
 
1142
+ ### Extracting Links in Tests
1143
+
1144
+ `extractLinks` from `@visulima/email/test` returns the http(s) URLs in a message, so a test can follow a verification or reset link. It returns `href` targets from the HTML, then bare URLs from the visible HTML text and from the plain text, without duplicates. Only `a` and `area` elements contribute `href` targets, so image sources, stylesheet and base links, CSS URLs, and the contents of `script`, `style` and comments are ignored. Entities are decoded in HTML, including the hexadecimal entities that template engines use to escape `=`.
1145
+
1146
+ Pass markup as `{ html }`. A bare string is always treated as plain text.
1147
+
1148
+ ```typescript
1149
+ import { createTestEmail, extractLinks } from "@visulima/email/test";
1150
+
1151
+ const email = createTestEmail();
1152
+
1153
+ await email.mail.send({
1154
+ from: { email: "app@example.com" },
1155
+ html: '<a href="https://app.example.com/verify?token=abc&amp;lang=en">Verify</a>',
1156
+ subject: "Verify",
1157
+ to: { email: "user@example.com" },
1158
+ });
1159
+
1160
+ const [verifyUrl] = extractLinks(email.sent()[0]!.options);
1161
+ ```
1162
+
1125
1163
  ### MailCrab Provider (for Development)
1126
1164
 
1127
1165
  MailCrab is a local SMTP server for testing emails during development. This provider is a convenience wrapper around the SMTP provider with MailCrab defaults.
@@ -1148,6 +1186,23 @@ const result = await mail.send(message);
1148
1186
 
1149
1187
  **Note:** Make sure MailCrab is running locally before using this provider. MailCrab can be installed via Docker or as a standalone application.
1150
1188
 
1189
+ ### File Provider (for Development and Testing)
1190
+
1191
+ The file provider writes each message to an `.eml` file in a directory instead of sending it. Use it to inspect the exact MIME output during development or in tests.
1192
+
1193
+ ```typescript
1194
+ import { createMail, MailMessage } from "@visulima/email";
1195
+ import { fileProvider } from "@visulima/email/providers/file";
1196
+
1197
+ const mail = createMail(fileProvider({ directory: "./tmp/emails" }));
1198
+
1199
+ const result = await mail.send(new MailMessage().to("user@example.com").from("sender@example.com").subject("Test Email").html("<h1>Test</h1>"));
1200
+
1201
+ // result.data.response.path is the written file
1202
+ ```
1203
+
1204
+ Files are named `<epoch-ms>-<message-id>.eml` and are never overwritten. A Message-ID that contains path separators is sanitised before it becomes part of the filename. Bcc recipients are written into the file, since nothing is delivered.
1205
+
1151
1206
  ## Supported Providers
1152
1207
 
1153
1208
  - **AWS SES** - Amazon Simple Email Service
@@ -1155,6 +1210,7 @@ const result = await mail.send(message);
1155
1210
  - **Failover** - Automatic failover between multiple providers
1156
1211
  - **HTTP** - Generic HTTP API provider
1157
1212
  - **MailCrab** - Local development email testing (SMTP wrapper)
1213
+ - **File** - Writes messages to `.eml` files instead of sending (Node.js only)
1158
1214
  - **Mailgun** - Developer-friendly email API service
1159
1215
  - **Mailjet** - Email marketing and transactional email platform
1160
1216
  - **MailerSend** - Modern email API for developers
@@ -1225,6 +1281,7 @@ These providers require Node.js built-in modules and only work in **Node.js**:
1225
1281
  - ⚠️ **AWS SES** - Requires `node:crypto` for AWS Signature V4 signing
1226
1282
  - ⚠️ **SMTP** - Requires `node:net` and `node:tls` for SMTP connections
1227
1283
  - ⚠️ **MailCrab** - Wraps SMTP provider (requires Node.js)
1284
+ - ⚠️ **File** - Writes to the local filesystem (requires Node.js)
1228
1285
  - ⚠️ **Nodemailer** - Requires the `nodemailer` package (Node.js only)
1229
1286
 
1230
1287
  ### Template Engines
@@ -1237,6 +1294,21 @@ Template engines are optional peer dependencies and work where their underlying
1237
1294
  - **Vue Email** - Works in Node.js and Bun (where `@vue-email/render` package is available)
1238
1295
  - **HTML-to-Text** - Works in Node.js and Bun (where `html-to-text` package is available)
1239
1296
 
1297
+ ### Locale Template Files (Node.js)
1298
+
1299
+ `@visulima/email/template/locale-files` loads templates from a directory, one file per locale. For a template named `welcome`, `welcome.de.html` is used for `de` (and `de-AT`), and `welcome.html` is the fallback, registered under `defaultLocale`:
1300
+
1301
+ ```typescript
1302
+ import { renderHandlebars } from "@visulima/email/template/handlebars";
1303
+ import { createLocaleFileRenderer } from "@visulima/email/template/locale-files";
1304
+
1305
+ const render = createLocaleFileRenderer("./emails", renderHandlebars, { defaultLocale: "en" });
1306
+
1307
+ const html = await render("welcome", "de-AT", { name: "Ada" });
1308
+ ```
1309
+
1310
+ Only file names from the directory listing are read, and a requested locale only selects a key, so a locale from user input never becomes a path. Templates are read on every call; cache the result if they do not change at runtime. Layouts are not part of this loader.
1311
+
1240
1312
  ### Runtime Requirements Summary
1241
1313
 
1242
1314
  | Provider | Node.js | Bun | Cloudflare Workers |
@@ -1265,6 +1337,7 @@ Template engines are optional peer dependencies and work where their underlying
1265
1337
  | AWS SES | ✅ | ❌ | ❌ |
1266
1338
  | SMTP | ✅ | ❌ | ❌ |
1267
1339
  | MailCrab | ✅ | ❌ | ❌ |
1340
+ | File | ✅ | ❌ | ❌ |
1268
1341
  | Nodemailer | ✅ | ❌ | ❌ |
1269
1342
  | Failover | ✅\* | ✅\* | ✅\* |
1270
1343
  | Round Robin | ✅\* | ✅\* | ✅\* |
@@ -1386,7 +1459,7 @@ See [Middleware](https://visulima.com/docs/package/email/middleware/introduction
1386
1459
 
1387
1460
  ## Queue & Worker
1388
1461
 
1389
- Durable background sending via the `EmailQueue` interface: an in-memory `MemoryQueue`, an `UnstorageQueue` (`@visulima/email/queue/unstorage`) backed by any [unstorage](https://unstorage.unjs.io/) store (Redis/FS/KV/Postgres), and a `createWorker(...)` with bounded concurrency, retry/backoff and dead-lettering.
1462
+ Durable background sending via the `EmailQueue` interface: an in-memory `MemoryQueue`, an `UnstorageQueue` (`@visulima/email/queue/unstorage`) backed by any [unstorage](https://unstorage.unjs.io/) store (Redis/FS/KV/Postgres), and a `createWorker(...)` with bounded concurrency, retry/backoff and dead-lettering. Permanent failures, such as HTTP 4xx responses other than `408`, `409` and `429`, are dead-lettered on their first attempt.
1390
1463
 
1391
1464
  ```typescript
1392
1465
  import { createMail } from "@visulima/email";
@@ -1,6 +1,6 @@
1
1
  import { KeyObject } from 'node:crypto';
2
- import { D as DkimOptions, S as SmimeEncryptOptions, E as EmailEncrypter, a as SmimeSignOptions, b as EmailSigner } from "../packem_shared/types.d-Cb5fls_X.js";
3
- import { E as EmailOptions } from "../packem_shared/types.d-B9295M4_.js";
2
+ import { D as DkimOptions, S as SmimeEncryptOptions, E as EmailEncrypter, a as SmimeSignOptions, b as EmailSigner } from "../packem_shared/types.d-A1Eea-p_.js";
3
+ import { E as EmailOptions } from "../packem_shared/types.d-CyiC9uRC.js";
4
4
  import 'node:buffer';
5
5
  /**
6
6
  * The signature algorithm for the ARC headers. RSA per RFC 8617, Ed25519 per RFC 8463.
@@ -1 +1 @@
1
- import c from"../packem_shared/EmailError-BIaHK1Uw.js";const g=r=>r==null?[]:Array.isArray(r)?r:[r],e=r=>{if(typeof r=="string")return r;if(typeof r=="number"||typeof r=="boolean")return String(r)},u=r=>{const a=e(r);if(a===void 0)return;const i=Number(a);return Number.isNaN(i)?void 0:i},f=r=>g(r).map(a=>({domain:e(a.domain),result:e(a.result)})),_=async r=>{let a;try{({XMLParser:a}=await import("fast-xml-parser"))}catch(t){throw t instanceof Error&&(t.code==="ERR_MODULE_NOT_FOUND"||t.message.includes("Cannot find module")||t.message.includes("Cannot find package"))?new c("deliverability","fast-xml-parser is not installed. Please install it: pnpm add fast-xml-parser",{cause:t}):new c("deliverability",`Failed to load fast-xml-parser: ${t.message}`,{cause:t})}let i;try{i=new a({ignoreAttributes:!0,parseTagValue:!1}).parse(r)}catch(t){throw new c("deliverability",`Failed to parse DMARC report: ${t.message}`,{cause:t})}const n=i.feedback??{},o=n.report_metadata??{},m=o.date_range??{},s=n.policy_published??{},y=g(n.record).map(t=>{const d=t.row??{},p=d.policy_evaluated??{},b=t.identifiers??{},l=t.auth_results??{};return{authResults:{dkim:f(l.dkim),spf:f(l.spf)},count:u(d.count)??0,disposition:e(p.disposition),dkim:e(p.dkim),headerFrom:e(b.header_from),sourceIp:e(d.source_ip),spf:e(p.spf)}});return{dateRangeBegin:e(m.begin),dateRangeEnd:e(m.end),email:e(o.email),organizationName:e(o.org_name),policyPublished:{adkim:e(s.adkim),aspf:e(s.aspf),domain:e(s.domain),p:e(s.p),pct:u(s.pct),sp:e(s.sp)},records:y,reportId:e(o.report_id)}};export{_ as parseDmarcReport};
1
+ import c from"../packem_shared/EmailError-SXEFQEyA.js";const g=r=>r==null?[]:Array.isArray(r)?r:[r],e=r=>{if(typeof r=="string")return r;if(typeof r=="number"||typeof r=="boolean")return String(r)},u=r=>{const a=e(r);if(a===void 0)return;const i=Number(a);return Number.isNaN(i)?void 0:i},f=r=>g(r).map(a=>({domain:e(a.domain),result:e(a.result)})),_=async r=>{let a;try{({XMLParser:a}=await import("fast-xml-parser"))}catch(t){throw t instanceof Error&&(t.code==="ERR_MODULE_NOT_FOUND"||t.message.includes("Cannot find module")||t.message.includes("Cannot find package"))?new c("deliverability","fast-xml-parser is not installed. Please install it: pnpm add fast-xml-parser",{cause:t}):new c("deliverability",`Failed to load fast-xml-parser: ${t.message}`,{cause:t})}let i;try{i=new a({ignoreAttributes:!0,parseTagValue:!1}).parse(r)}catch(t){throw new c("deliverability",`Failed to parse DMARC report: ${t.message}`,{cause:t})}const n=i.feedback??{},o=n.report_metadata??{},m=o.date_range??{},s=n.policy_published??{},y=g(n.record).map(t=>{const d=t.row??{},p=d.policy_evaluated??{},b=t.identifiers??{},l=t.auth_results??{};return{authResults:{dkim:f(l.dkim),spf:f(l.spf)},count:u(d.count)??0,disposition:e(p.disposition),dkim:e(p.dkim),headerFrom:e(b.header_from),sourceIp:e(d.source_ip),spf:e(p.spf)}});return{dateRangeBegin:e(m.begin),dateRangeEnd:e(m.end),email:e(o.email),organizationName:e(o.org_name),policyPublished:{adkim:e(s.adkim),aspf:e(s.aspf),domain:e(s.domain),p:e(s.p),pct:u(s.pct),sp:e(s.sp)},records:y,reportId:e(o.report_id)}};export{_ as parseDmarcReport};
@@ -1,4 +1,4 @@
1
- import { c as EmailAddress } from "../packem_shared/types.d-B9295M4_.js";
1
+ import { c as EmailAddress } from "../packem_shared/types.d-CyiC9uRC.js";
2
2
  import { S as SuppressionReason, a as SuppressionEntry, b as SuppressionStore } from "../packem_shared/types.d-BzoHR9nB.js";
3
3
  import 'node:buffer';
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { M as MaybePromise } from "../packem_shared/types.d-B9295M4_.js";
1
+ import { M as MaybePromise } from "../packem_shared/types.d-CyiC9uRC.js";
2
2
  import 'node:buffer';
3
3
  /**
4
4
  * A parsed address from an inbound message.
package/dist/index.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- export { type A as AttachmentDataOptions, type a as AttachmentOptions, type B as BatchBase, type b as BatchRenderer, D as DraftMailMessage, type F as FeatureCheckMode, M as Mail, type c as MailGlobalConfig, d as MailMessage, type e as MailOptions, type P as Personalization, type S as SendBatchOptions, type f as SendableMessage, g as createMail, h as detectMimeType, i as generateContentId, r as readFileAsBuffer } from "./packem_shared/mail.d-BdyyyQO5.js";
2
- export type { M as Middleware, S as SendFunction } from "./packem_shared/types.d-GKqPNk0I.js";
3
- export { type a as Provider, type P as ProviderFactory, d as defineProvider } from "./packem_shared/provider.d-Cx5YQJdP.js";
1
+ export { type A as AttachmentDataOptions, type a as AttachmentOptions, type B as BatchBase, type b as BatchRenderer, D as DraftMailMessage, type F as FeatureCheckMode, M as Mail, type c as MailGlobalConfig, d as MailMessage, type e as MailOptions, type P as Personalization, type S as SendBatchOptions, type f as SendableMessage, g as createMail, h as detectMimeType, i as generateContentId, r as readFileAsBuffer } from "./packem_shared/mail.d-2u-30lWz.js";
2
+ export type { M as Middleware, S as SendFunction } from "./packem_shared/types.d-Chhzb-48.js";
3
+ export { type a as Provider, type P as ProviderFactory, d as defineProvider } from "./packem_shared/provider.d-BLLbEuQ8.js";
4
4
  export type { T as TemplateRenderer } from "./packem_shared/types.d-I-KeZ5w2.js";
5
- export type { A as Attachment, B as BaseConfig, C as CalendarEventOptions, c as EmailAddress, e as EmailHeaders, E as EmailOptions, a as EmailResult, d as EmailTag, F as FeatureFlags, I as ImmutableHeaders, M as MaybePromise, P as Priority, b as Receipt, R as Result } from "./packem_shared/types.d-B9295M4_.js";
5
+ export type { A as Attachment, B as BaseConfig, C as CalendarEventOptions, c as EmailAddress, e as EmailHeaders, E as EmailOptions, a as EmailResult, d as EmailTag, F as FeatureFlags, I as ImmutableHeaders, M as MaybePromise, P as Priority, b as Receipt, R as Result } from "./packem_shared/types.d-CyiC9uRC.js";
6
6
  export { default as parseEml } from "./utils/parse-eml.js";
7
7
  export { type OkResult, isErr, isOk, mapOk, tryAsync, unwrap, unwrapOr } from "./utils/result.js";
8
8
  export { type FeatureSupportResult, type FeatureViolation, default as checkFeatureSupport } from "./utils/validation/check-feature-support.js";
9
9
  import 'ical-generator';
10
10
  import 'node:buffer';
11
- import "./packem_shared/types.d-Cb5fls_X.js";
11
+ import "./packem_shared/types.d-A1Eea-p_.js";
12
12
  interface ErrorProperties {
13
13
  cause?: unknown;
14
14
  hint?: ErrorHint;
@@ -65,10 +65,16 @@ declare class VisulimaError extends Error {
65
65
  declare class EmailError extends VisulimaError {
66
66
  readonly component: string;
67
67
  readonly code?: string;
68
+ /**
69
+ * True when repeating the same request would fail the same way, so a queue worker should
70
+ * dead-letter the job instead of retrying it.
71
+ */
72
+ readonly permanent: boolean;
68
73
  constructor(component: string, message: string, options?: {
69
74
  cause?: Error | unknown;
70
75
  code?: string;
71
76
  hint?: string | string[];
77
+ permanent?: boolean;
72
78
  });
73
79
  }
74
80
  /**
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{detectMimeType as a,generateContentId as t,readFileAsBuffer as o}from"./packem_shared/detectMimeType-BRz3Qjyz.js";import{default as p}from"./packem_shared/DraftMailMessage-B0gwf8n_.js";import{default as m}from"./packem_shared/EmailError-BIaHK1Uw.js";import{default as l}from"./packem_shared/RequiredOptionError-ctDEEB1s.js";import{Mail as u,createMail as x}from"./packem_shared/Mail-DmYNQ0KK.js";import{default as M}from"./packem_shared/MailMessage-DmmaNHHc.js";import{defineProvider as E}from"./packem_shared/defineProvider-C9q-7iwt.js";import{default as g}from"./utils/parse-eml.js";import{isErr as y,isOk as w,mapOk as A,tryAsync as F,unwrap as h,unwrapOr as q}from"./utils/result.js";import{default as B}from"./utils/validation/check-feature-support.js";export{p as DraftMailMessage,m as EmailError,u as Mail,M as MailMessage,l as RequiredOptionError,B as checkFeatureSupport,x as createMail,E as defineProvider,a as detectMimeType,t as generateContentId,y as isErr,w as isOk,A as mapOk,g as parseEml,o as readFileAsBuffer,F as tryAsync,h as unwrap,q as unwrapOr};
1
+ import{detectMimeType as a,generateContentId as t,readFileAsBuffer as o}from"./packem_shared/detectMimeType-BRz3Qjyz.js";import{default as p}from"./packem_shared/DraftMailMessage-zkKTOxQ8.js";import{default as m}from"./packem_shared/EmailError-SXEFQEyA.js";import{default as l}from"./packem_shared/RequiredOptionError-C2wWqmiE.js";import{Mail as u,createMail as x}from"./packem_shared/Mail-DtpXUSIl.js";import{default as M}from"./packem_shared/MailMessage-Aw0vVAh-.js";import{defineProvider as E}from"./packem_shared/defineProvider-C9q-7iwt.js";import{default as g}from"./utils/parse-eml.js";import{isErr as y,isOk as w,mapOk as A,tryAsync as F,unwrap as h,unwrapOr as q}from"./utils/result.js";import{default as B}from"./utils/validation/check-feature-support.js";export{p as DraftMailMessage,m as EmailError,u as Mail,M as MailMessage,l as RequiredOptionError,B as checkFeatureSupport,x as createMail,E as defineProvider,a as detectMimeType,t as generateContentId,y as isErr,w as isOk,A as mapOk,g as parseEml,o as readFileAsBuffer,F as tryAsync,h as unwrap,q as unwrapOr};
@@ -1,6 +1,6 @@
1
- import { M as Middleware } from "../packem_shared/types.d-GKqPNk0I.js";
2
- export { type S as SendFunction, c as composeMiddleware } from "../packem_shared/types.d-GKqPNk0I.js";
3
- import { E as EmailOptions, M as MaybePromise, a as EmailResult, R as Result, c as EmailAddress } from "../packem_shared/types.d-B9295M4_.js";
1
+ import { M as Middleware } from "../packem_shared/types.d-Chhzb-48.js";
2
+ export { type S as SendFunction, c as composeMiddleware } from "../packem_shared/types.d-Chhzb-48.js";
3
+ import { E as EmailOptions, M as MaybePromise, a as EmailResult, R as Result, c as EmailAddress } from "../packem_shared/types.d-CyiC9uRC.js";
4
4
  import { d as EventBus } from "../packem_shared/event-bus.d-BNQA-FZE.js";
5
5
  import { b as SuppressionStore } from "../packem_shared/types.d-BzoHR9nB.js";
6
6
  import { T as Tracer } from "../packem_shared/tracer.d-sb5yrt_n.js";
@@ -1 +1 @@
1
- import{circuitBreakerMiddleware as o}from"../packem_shared/circuitBreakerMiddleware-DcBN7K6H.js";import{dedupeMiddleware as i}from"../packem_shared/dedupeMiddleware-BTo8Qjje.js";import{withEvents as p}from"../packem_shared/withEvents-BinK2hMw.js";import{loggingMiddleware as f}from"../packem_shared/loggingMiddleware-DNVf8O1B.js";import{withMetrics as w}from"../packem_shared/withMetrics-i3mZVfeE.js";import{oauth2Middleware as l}from"../packem_shared/oauth2Middleware-C0M1w3_2.js";import{RATE_LIMIT_PRESETS as h,rateLimitMiddleware as s}from"../packem_shared/RATE_LIMIT_PRESETS-BIFmRuh2.js";import{default as c}from"../packem_shared/withRender-Ce6leoYE.js";import{retryMiddleware as E}from"../packem_shared/retryMiddleware-CaTv0UIm.js";import{withSuppression as g}from"../packem_shared/withSuppression-B0eZ2xNQ.js";import{withTelemetry as S}from"../packem_shared/withTelemetry-DEKTis1I.js";import{composeMiddleware as I}from"../packem_shared/composeMiddleware-B3RCDFsy.js";export{h as RATE_LIMIT_PRESETS,o as circuitBreakerMiddleware,I as composeMiddleware,i as dedupeMiddleware,f as loggingMiddleware,l as oauth2Middleware,s as rateLimitMiddleware,E as retryMiddleware,p as withEvents,w as withMetrics,c as withRender,g as withSuppression,S as withTelemetry};
1
+ import{circuitBreakerMiddleware as o}from"../packem_shared/circuitBreakerMiddleware-Cm0m75qi.js";import{dedupeMiddleware as i}from"../packem_shared/dedupeMiddleware-BTo8Qjje.js";import{withEvents as p}from"../packem_shared/withEvents-BinK2hMw.js";import{loggingMiddleware as f}from"../packem_shared/loggingMiddleware-DNVf8O1B.js";import{withMetrics as w}from"../packem_shared/withMetrics-i3mZVfeE.js";import{oauth2Middleware as l}from"../packem_shared/oauth2Middleware-C0M1w3_2.js";import{RATE_LIMIT_PRESETS as h,rateLimitMiddleware as s}from"../packem_shared/RATE_LIMIT_PRESETS-BIFmRuh2.js";import{default as c}from"../packem_shared/withRender-Ce6leoYE.js";import{retryMiddleware as E}from"../packem_shared/retryMiddleware-CaTv0UIm.js";import{withSuppression as g}from"../packem_shared/withSuppression-COCHL9Zx.js";import{withTelemetry as S}from"../packem_shared/withTelemetry-DEKTis1I.js";import{composeMiddleware as I}from"../packem_shared/composeMiddleware-B3RCDFsy.js";export{h as RATE_LIMIT_PRESETS,o as circuitBreakerMiddleware,I as composeMiddleware,i as dedupeMiddleware,f as loggingMiddleware,l as oauth2Middleware,s as rateLimitMiddleware,E as retryMiddleware,p as withEvents,w as withMetrics,c as withRender,g as withSuppression,S as withTelemetry};