@visulima/email 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +57 -1
  3. package/dist/deliverability/dmarc-report.js +1 -1
  4. package/dist/index.d.ts +6 -0
  5. package/dist/index.js +1 -1
  6. package/dist/middleware/index.js +1 -1
  7. package/dist/packem_chunks/index.js +5 -5
  8. package/dist/packem_shared/EmailError-SXEFQEyA.js +1 -0
  9. package/dist/packem_shared/{Mail-DmYNQ0KK.js → Mail-0cxS6jre.js} +1 -1
  10. package/dist/packem_shared/{RequiredOptionError-ctDEEB1s.js → RequiredOptionError-C2wWqmiE.js} +1 -1
  11. package/dist/packem_shared/{ahaSendProvider-D95-pwFh.js → ahaSendProvider-Ci_a_O8d.js} +1 -1
  12. package/dist/packem_shared/{attachment-processor-BJFDS343.js → attachment-processor-AB1m-n9T.js} +1 -1
  13. package/dist/packem_shared/{autoSendProvider-yxUx3sEj.js → autoSendProvider-DG3pNJML.js} +1 -1
  14. package/dist/packem_shared/{awsSesProvider-DnZo9X_g.js → awsSesProvider-C4b5kuL4.js} +1 -1
  15. package/dist/packem_shared/{azureProvider-D6Z4c1yT.js → azureProvider-CEnz3uy_.js} +1 -1
  16. package/dist/packem_shared/{brevoProvider-D1yTSs0l.js → brevoProvider-BsbInXjw.js} +1 -1
  17. package/dist/packem_shared/{build-mime-message-BCFKPqaW.js → build-mime-message-D45WdgfP.js} +1 -1
  18. package/dist/packem_shared/{circuitBreakerMiddleware-DcBN7K6H.js → circuitBreakerMiddleware-Cm0m75qi.js} +1 -1
  19. package/dist/packem_shared/{cloudflareEmailProvider-DZbs35NL.js → cloudflareEmailProvider-CnpH5R5p.js} +1 -1
  20. package/dist/packem_shared/{createTestEmail-gr512snl.js → createTestEmail-DWimjf0F.js} +1 -1
  21. package/dist/packem_shared/createWorker-ChrQvLUF.js +1 -0
  22. package/dist/packem_shared/extractLinks-BwE1SjNF.js +1 -0
  23. package/dist/packem_shared/{failoverProvider-IED1M0M5.js → failoverProvider-DsBqMOfK.js} +1 -1
  24. package/dist/packem_shared/fileProvider-BnDeaSqA.js +1 -0
  25. package/dist/packem_shared/{httpProvider-CpdWJoRq.js → httpProvider-BuA00407.js} +1 -1
  26. package/dist/packem_shared/{infobipProvider-DTqFu5pM.js → infobipProvider-DVUg9jKC.js} +1 -1
  27. package/dist/packem_shared/{isValidSigningCertUrl-CK1t2a7x.js → isValidSigningCertUrl-gnYr3oyD.js} +1 -1
  28. package/dist/packem_shared/{loopsProvider-PiE2HxoW.js → loopsProvider-BJUNQ2rR.js} +1 -1
  29. package/dist/packem_shared/{mailCrabProvider-BSapw_mS.js → mailCrabProvider-CFKRofS5.js} +1 -1
  30. package/dist/packem_shared/{mailPaceProvider-BugIutTw.js → mailPaceProvider-DBQd6NYt.js} +1 -1
  31. package/dist/packem_shared/{mailchannelsProvider-Dn24mI8D.js → mailchannelsProvider-CPVW3wlH.js} +1 -1
  32. package/dist/packem_shared/{mailerSendProvider-B6X4Lhsf.js → mailerSendProvider-B2H6BtoV.js} +1 -1
  33. package/dist/packem_shared/{mailgunProvider-4sMtcQcW.js → mailgunProvider-JRVH70PB.js} +1 -1
  34. package/dist/packem_shared/{mailjetProvider-DvrHwipw.js → mailjetProvider-BzBovF5M.js} +1 -1
  35. package/dist/packem_shared/{mailomatProvider-CrkNQGfL.js → mailomatProvider-x6IHKuWU.js} +1 -1
  36. package/dist/packem_shared/{mailtrapProvider-DTgvCUrs.js → mailtrapProvider-CJTJoDcV.js} +1 -1
  37. package/dist/packem_shared/make-request-mszP240P.js +1 -0
  38. package/dist/packem_shared/{mandrillProvider-BaK0GEj7.js → mandrillProvider-BcztgKkr.js} +1 -1
  39. package/dist/packem_shared/{mockProvider-CTxxbT0d.js → mockProvider-DVtl5Jjy.js} +1 -1
  40. package/dist/packem_shared/{netcoreProvider-BZJ0noQ2.js → netcoreProvider-YWEpa-wi.js} +1 -1
  41. package/dist/packem_shared/{nodemailerProvider-DqJh8ghN.js → nodemailerProvider-WNNtLE2s.js} +1 -1
  42. package/dist/packem_shared/{opentelemetryProvider-s0gvh05u.js → opentelemetryProvider-ddbILMeS.js} +1 -1
  43. package/dist/packem_shared/{outlook365Provider-DQUSFy2d.js → outlook365Provider-D5eETMZq.js} +1 -1
  44. package/dist/packem_shared/{plunkProvider-WMi1yPya.js → plunkProvider-TrBBoX7_.js} +1 -1
  45. package/dist/packem_shared/{postalProvider-DpBFGpoX.js → postalProvider-CU53hX0R.js} +1 -1
  46. package/dist/packem_shared/{postmarkProvider-ClUaHPvw.js → postmarkProvider-DEYQp8Gv.js} +1 -1
  47. package/dist/packem_shared/{provider-base-BEz00m9R.js → provider-base-G22b4a87.js} +1 -1
  48. package/dist/packem_shared/{resendProvider-DtaB7sk0.js → resendProvider-DC7Op5Gr.js} +1 -1
  49. package/dist/packem_shared/{retry-aghj7y8m.js → retry-ByPJFAtq.js} +1 -1
  50. package/dist/packem_shared/{roundRobinProvider-Dr1DdT2L.js → roundRobinProvider-CfuNY7We.js} +1 -1
  51. package/dist/packem_shared/{scalewayProvider-BhbDUEah.js → scalewayProvider-x6S2ZSPd.js} +1 -1
  52. package/dist/packem_shared/{sendGridProvider-DBZHhxd6.js → sendGridProvider-8Po3P-ia.js} +1 -1
  53. package/dist/packem_shared/{smtpProvider-CjcOEiAh.js → smtpProvider-BPamDNuv.js} +1 -1
  54. package/dist/packem_shared/{sparkpostProvider-B19lU9D5.js → sparkpostProvider-BVu1KeNz.js} +1 -1
  55. package/dist/packem_shared/{sweegoProvider-H8KWGDff.js → sweegoProvider-DHISjS9s.js} +1 -1
  56. package/dist/packem_shared/{verifySendGridWebhook-csuRasH3.js → verifySendGridWebhook-0_3qKQA1.js} +1 -1
  57. package/dist/packem_shared/{withSuppression-B0eZ2xNQ.js → withSuppression-COCHL9Zx.js} +1 -1
  58. package/dist/packem_shared/{zeptomailProvider-L7nkdn0L.js → zeptomailProvider-IhCIl_2a.js} +1 -1
  59. package/dist/providers/ahasend/index.js +1 -1
  60. package/dist/providers/autosend/index.js +1 -1
  61. package/dist/providers/aws-ses/index.js +1 -1
  62. package/dist/providers/azure/index.js +1 -1
  63. package/dist/providers/brevo/index.js +1 -1
  64. package/dist/providers/cloudflare-email/index.js +1 -1
  65. package/dist/providers/failover/index.js +1 -1
  66. package/dist/providers/file/index.d.ts +23 -0
  67. package/dist/providers/file/index.js +1 -0
  68. package/dist/providers/http/index.js +1 -1
  69. package/dist/providers/infobip/index.js +1 -1
  70. package/dist/providers/loops/index.js +1 -1
  71. package/dist/providers/mailchannels/index.js +1 -1
  72. package/dist/providers/mailcrab/index.js +1 -1
  73. package/dist/providers/mailersend/index.js +1 -1
  74. package/dist/providers/mailgun/index.js +1 -1
  75. package/dist/providers/mailjet/index.js +1 -1
  76. package/dist/providers/mailomat/index.js +1 -1
  77. package/dist/providers/mailpace/index.js +1 -1
  78. package/dist/providers/mailtrap/index.js +1 -1
  79. package/dist/providers/mandrill/index.js +1 -1
  80. package/dist/providers/mock/index.js +1 -1
  81. package/dist/providers/netcore/index.js +1 -1
  82. package/dist/providers/nodemailer/index.js +1 -1
  83. package/dist/providers/opentelemetry/index.js +1 -1
  84. package/dist/providers/outlook365/index.js +1 -1
  85. package/dist/providers/plunk/index.js +1 -1
  86. package/dist/providers/postal/index.js +1 -1
  87. package/dist/providers/postmark/index.js +1 -1
  88. package/dist/providers/resend/index.js +1 -1
  89. package/dist/providers/roundrobin/index.js +1 -1
  90. package/dist/providers/scaleway/index.js +1 -1
  91. package/dist/providers/sendgrid/index.js +1 -1
  92. package/dist/providers/smtp/index.js +1 -1
  93. package/dist/providers/sparkpost/index.js +1 -1
  94. package/dist/providers/sweego/index.js +1 -1
  95. package/dist/providers/zeptomail/index.js +1 -1
  96. package/dist/queue/index.d.ts +2 -1
  97. package/dist/queue/index.js +1 -1
  98. package/dist/render/css-inline.js +1 -1
  99. package/dist/template-engines/handlebars.js +1 -1
  100. package/dist/template-engines/html-to-text.js +1 -1
  101. package/dist/template-engines/i18n.js +1 -1
  102. package/dist/template-engines/jsx-email.js +1 -1
  103. package/dist/template-engines/liquid.js +1 -1
  104. package/dist/template-engines/locale-files.d.ts +66 -0
  105. package/dist/template-engines/locale-files.js +1 -0
  106. package/dist/template-engines/mjml.js +1 -1
  107. package/dist/template-engines/react-email.js +1 -1
  108. package/dist/template-engines/vue-email.js +1 -1
  109. package/dist/test/index.d.ts +19 -2
  110. package/dist/test/index.js +1 -1
  111. package/dist/utils/format-email-address.js +1 -1
  112. package/dist/utils/result.js +1 -1
  113. package/dist/webhooks/index.js +1 -1
  114. package/package.json +9 -1
  115. package/dist/packem_shared/EmailError-BIaHK1Uw.js +0 -1
  116. package/dist/packem_shared/createWorker-ChuTgETP.js +0 -1
  117. package/dist/packem_shared/make-request-Cyz5_yu8.js +0 -1
@@ -1 +1 @@
1
- import e from"juice";import n from"../packem_shared/EmailError-BIaHK1Uw.js";const s=(t,o)=>{const i=e.default??e;try{return i(t,o)}catch(r){throw new n("render",`Failed to inline CSS: ${r.message}`,{cause:r})}};export{s as default};
1
+ import e from"juice";import n from"../packem_shared/EmailError-SXEFQEyA.js";const s=(t,o)=>{const i=e.default??e;try{return i(t,o)}catch(r){throw new n("render",`Failed to inline CSS: ${r.message}`,{cause:r})}};export{s as default};
@@ -1 +1 @@
1
- import t from"handlebars";import s from"../packem_shared/EmailError-BIaHK1Uw.js";const i=(r,n,e)=>{try{if(typeof r!="string")throw new TypeError("Handlebars template must be a string");return t.compile(r,e)(n??{})}catch(a){throw a instanceof Error&&a.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:a}):new s("handlebars",`Failed to render Handlebars template: ${a.message}`,{cause:a})}},d=(r,n)=>{try{t.registerHelper(r,n)}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:e}):e}},c=(r,n)=>{try{t.registerPartial(r,n)}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:e}):e}};export{d as registerHandlebarsHelper,c as registerHandlebarsPartial,i as renderHandlebars};
1
+ import t from"handlebars";import s from"../packem_shared/EmailError-SXEFQEyA.js";const i=(r,n,e)=>{try{if(typeof r!="string")throw new TypeError("Handlebars template must be a string");return t.compile(r,e)(n??{})}catch(a){throw a instanceof Error&&a.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:a}):new s("handlebars",`Failed to render Handlebars template: ${a.message}`,{cause:a})}},d=(r,n)=>{try{t.registerHelper(r,n)}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:e}):e}},c=(r,n)=>{try{t.registerPartial(r,n)}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new s("handlebars","Handlebars is not installed. Please install it: pnpm add handlebars",{cause:e}):e}};export{d as registerHandlebarsHelper,c as registerHandlebarsPartial,i as renderHandlebars};
@@ -1 +1 @@
1
- import{convert as l}from"html-to-text";import r from"../packem_shared/EmailError-BIaHK1Uw.js";const n=(o,t)=>{try{return l(o,{longWordSplit:t?.longWordSplit,preserveNewlines:t?.preserveNewlines??!1,selectors:t?.selectors,wordwrap:t?.wordwrap??80})}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new r("html-to-text","html-to-text is not installed. Please install it: pnpm add html-to-text",{cause:e}):new r("html-to-text",`Failed to convert HTML to text: ${e.message}`,{cause:e})}};export{n as default};
1
+ import{convert as l}from"html-to-text";import r from"../packem_shared/EmailError-SXEFQEyA.js";const n=(o,t)=>{try{return l(o,{longWordSplit:t?.longWordSplit,preserveNewlines:t?.preserveNewlines??!1,selectors:t?.selectors,wordwrap:t?.wordwrap??80})}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new r("html-to-text","html-to-text is not installed. Please install it: pnpm add html-to-text",{cause:e}):new r("html-to-text",`Failed to convert HTML to text: ${e.message}`,{cause:e})}};export{n as default};
@@ -1 +1 @@
1
- import c from"../packem_shared/EmailError-BIaHK1Uw.js";const l=(t,o,n)=>{const a=new Map(Object.keys(t).map(r=>[r.toLowerCase(),r])),e=r=>{if(r!==void 0)return a.get(r.toLowerCase())};return e(o)??e(o.split("-")[0])??e(n)},d=async(t,o,n,a,e)=>{const r=l(t,o,e?.defaultLocale);if(r===void 0)throw new c("i18n",`No template found for locale "${o}"${e?.defaultLocale?` or default "${e.defaultLocale}"`:""}`);return await n(t[r],a,e?.rendererOptions)},u=(t,o)=>(n,a,e,r)=>d(n,a,t,e,{defaultLocale:o,rendererOptions:r});export{u as createI18nRenderer,d as renderI18n,l as resolveLocale};
1
+ import c from"../packem_shared/EmailError-SXEFQEyA.js";const l=(t,o,n)=>{const a=new Map(Object.keys(t).map(r=>[r.toLowerCase(),r])),e=r=>{if(r!==void 0)return a.get(r.toLowerCase())};return e(o)??e(o.split("-")[0])??e(n)},d=async(t,o,n,a,e)=>{const r=l(t,o,e?.defaultLocale);if(r===void 0)throw new c("i18n",`No template found for locale "${o}"${e?.defaultLocale?` or default "${e.defaultLocale}"`:""}`);return await n(t[r],a,e?.rendererOptions)},u=(t,o)=>(n,a,e,r)=>d(n,a,t,e,{defaultLocale:o,rendererOptions:r});export{u as createI18nRenderer,d as renderI18n,l as resolveLocale};
@@ -1 +1 @@
1
- import t from"../packem_shared/EmailError-BIaHK1Uw.js";const i=async(n,r,a)=>{try{const{render:e}=await import("jsx-email");return await e(n,{plainText:a?.plainText,pretty:a?.pretty})}catch(e){throw e instanceof Error&&(e.code==="ERR_MODULE_NOT_FOUND"||e.message.includes("Cannot find module")||e.message.includes("Cannot find package"))?new t("jsx-email","jsx-email is not installed. Please install it: pnpm add jsx-email",{cause:e}):new t("jsx-email",`Failed to render jsx-email component: ${e.message}`,{cause:e})}};export{i as default};
1
+ import t from"../packem_shared/EmailError-SXEFQEyA.js";const i=async(n,r,a)=>{try{const{render:e}=await import("jsx-email");return await e(n,{plainText:a?.plainText,pretty:a?.pretty})}catch(e){throw e instanceof Error&&(e.code==="ERR_MODULE_NOT_FOUND"||e.message.includes("Cannot find module")||e.message.includes("Cannot find package"))?new t("jsx-email","jsx-email is not installed. Please install it: pnpm add jsx-email",{cause:e}):new t("jsx-email",`Failed to render jsx-email component: ${e.message}`,{cause:e})}};export{i as default};
@@ -1 +1 @@
1
- import n from"../packem_shared/EmailError-BIaHK1Uw.js";const o=async(i,r,t)=>{try{if(typeof i!="string")throw new TypeError("Liquid template must be a string");const{Liquid:e}=await import("liquidjs");return await new e(t??{}).parseAndRender(i,r??{})}catch(e){throw e instanceof TypeError?e:e instanceof Error&&(e.code==="ERR_MODULE_NOT_FOUND"||e.message.includes("Cannot find module")||e.message.includes("Cannot find package"))?new n("liquid","liquidjs is not installed. Please install it: pnpm add liquidjs",{cause:e}):new n("liquid",`Failed to render Liquid template: ${e.message}`,{cause:e})}};export{o as default};
1
+ import n from"../packem_shared/EmailError-SXEFQEyA.js";const o=async(i,r,t)=>{try{if(typeof i!="string")throw new TypeError("Liquid template must be a string");const{Liquid:e}=await import("liquidjs");return await new e(t??{}).parseAndRender(i,r??{})}catch(e){throw e instanceof TypeError?e:e instanceof Error&&(e.code==="ERR_MODULE_NOT_FOUND"||e.message.includes("Cannot find module")||e.message.includes("Cannot find package"))?new n("liquid","liquidjs is not installed. Please install it: pnpm add liquidjs",{cause:e}):new n("liquid",`Failed to render Liquid template: ${e.message}`,{cause:e})}};export{o as default};
@@ -0,0 +1,66 @@
1
+ import { LocaleTemplates } from "./i18n.js";
2
+ import { T as TemplateRenderer } from "../packem_shared/types.d-I-KeZ5w2.js";
3
+ /**
4
+ * Loads a named template in every locale that has a file on disk.
5
+ *
6
+ * For a name such as `welcome`, the directory is searched for `welcome.{locale}.html`, and each
7
+ * match is keyed by its locale. The un-suffixed `welcome.html` is registered under `defaultLocale`.
8
+ * The result plugs straight into {@link renderI18n}, which then resolves `de-AT` to `de` and falls
9
+ * back to the default locale.
10
+ *
11
+ * Only files are read, and only names built from the directory listing. The requested name must not
12
+ * contain a path separator or `..`, and a locale is never turned into a path, so a locale taken from
13
+ * user input (such as `Accept-Language`) only selects a key in the returned map.
14
+ * @param directory The directory that holds the template files.
15
+ * @param name The template name, without locale or extension.
16
+ * @param options The default locale and file extension. See {@link LocaleFileOptions}.
17
+ * @returns The file contents keyed by locale. See {@link LocaleTemplates}.
18
+ * @throws {EmailError} When the name is unsafe, the directory cannot be read, no file matches, the
19
+ * un-suffixed file has no `defaultLocale`, the default locale has both files, or two locales differ
20
+ * only by case.
21
+ * @example
22
+ * ```ts
23
+ * const templates = await loadLocaleTemplates("./emails", "welcome", { defaultLocale: "en" });
24
+ * await renderI18n(templates, "de-AT", renderHandlebars, { name: "Ada" }, { defaultLocale: "en" });
25
+ * ```
26
+ */
27
+ declare const loadLocaleTemplates: (directory: string, name: string, options?: LocaleFileOptions) => Promise<LocaleTemplates>;
28
+ /**
29
+ * Creates a renderer that loads a named template from disk for a locale and renders it with an
30
+ * engine, falling back to the default locale. Binding `defaultLocale` once here means the fallback
31
+ * cannot be left out of a call.
32
+ * @param directory The directory that holds the template files.
33
+ * @param renderer The text template engine, such as `renderHandlebars` or `renderMjml`.
34
+ * @param options The default locale and file extension. See {@link LocaleFileOptions}.
35
+ * @returns A {@link LocaleFileRenderer}.
36
+ * @example
37
+ * ```ts
38
+ * import { renderHandlebars } from "@visulima/email/template/handlebars";
39
+ * import { createLocaleFileRenderer } from "@visulima/email/template/locale-files";
40
+ *
41
+ * const render = createLocaleFileRenderer("./emails", renderHandlebars, { defaultLocale: "en" });
42
+ * await render("welcome", "fr", { name: "Ada" }); // reads welcome.html when there is no welcome.fr.html
43
+ * ```
44
+ */
45
+ declare const createLocaleFileRenderer: (directory: string, renderer: TemplateRenderer, options?: LocaleFileOptions) => LocaleFileRenderer;
46
+ /**
47
+ * Options for {@link loadLocaleTemplates} and {@link createLocaleFileRenderer}.
48
+ */
49
+ interface LocaleFileOptions {
50
+ /**
51
+ * Locale the un-suffixed file (`name` plus `extension`) is registered under. It is also the
52
+ * fallback for any locale without its own file. Required when that file exists.
53
+ */
54
+ defaultLocale?: string;
55
+ /**
56
+ * File extension, including the leading dot.
57
+ * @default ".html"
58
+ */
59
+ extension?: string;
60
+ }
61
+ /**
62
+ * Renders a named template from disk for a locale. Each call reads the directory again, so cache
63
+ * the result yourself if the templates do not change at runtime.
64
+ */
65
+ type LocaleFileRenderer = (name: string, locale: string, data?: Record<string, unknown>, rendererOptions?: Record<string, unknown>) => Promise<string>;
66
+ export { LocaleFileOptions, LocaleFileRenderer, createLocaleFileRenderer, loadLocaleTemplates };
@@ -0,0 +1 @@
1
+ import{createRequire as _}from"node:module";import l from"../packem_shared/EmailError-SXEFQEyA.js";import{renderI18n as y}from"./i18n.js";let L;const F=t=>(L??=_(import.meta.url))(t),d=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,w=t=>{if(typeof d<"u"&&d.versions&&d.versions.node){const[e,s]=d.versions.node.split(".").map(Number);if(e>22||e===22&&s>=3||e===20&&s>=16)return d.getBuiltinModule(t)}return F(t)},{readFile:h,readdir:g}=w("node:fs/promises"),{join:m}=w("node:path"),c="template-files",T=".html",b=/^[\da-z-]+$/i,v=["/","\\","\0",".."],p=t=>v.some(e=>t.includes(e)),E=(t,e)=>{if(t===""||p(t))throw new l(c,`Invalid template name "${t}"`);if(!e.startsWith(".")||e.length<2||p(e))throw new l(c,`Invalid template extension "${e}"`)},j=async t=>{try{return await g(t,{withFileTypes:!0})}catch(e){throw new l(c,`Cannot read template directory "${t}"`,{cause:e})}},N=(t,e,s)=>{const o=`${e}${s}`,n=`${e}.`,r=new Map;let i=!1;for(const a of t)if(a.isFile()){if(a.name===o)i=!0;else if(a.name.startsWith(n)&&a.name.endsWith(s)){const f=a.name.slice(n.length,a.name.length-s.length);b.test(f)&&r.set(f,a.name)}}return{hasBaseFile:i,localeFiles:r}},C=t=>{const e=new Map;for(const s of t){const o=e.get(s.toLowerCase());if(o!==void 0)throw new l(c,`Locales "${o}" and "${s}" differ only by case`);e.set(s.toLowerCase(),s)}},R=async(t,e)=>{const s=await Promise.all([...e].map(async([o,n])=>[o,await h(m(t,n),"utf8")]));return Object.fromEntries(s)},I=async(t,e,s={})=>{const o=s.extension??T,{defaultLocale:n}=s,r=`${e}${o}`;E(e,o);const{hasBaseFile:i,localeFiles:a}=N(await j(t),e,o),f=[...a.keys()];if(i&&n===void 0)throw new l(c,`"${r}" exists, so pass defaultLocale to register it`);if(!i&&f.length===0)throw new l(c,`No "${e}" template files with extension "${o}" in "${t}"`);if(C(f),i&&n!==void 0&&f.some($=>$.toLowerCase()===n.toLowerCase()))throw new l(c,`"${r}" and "${e}.${n}${o}" both exist; keep one`);const u=await R(t,a);return i&&n!==void 0&&(u[n]=await h(m(t,r),"utf8")),u},D=(t,e,s={})=>async(o,n,r,i)=>{const a=await I(t,o,s);return y(a,n,e,r,{defaultLocale:s.defaultLocale,rendererOptions:i})};export{D as createLocaleFileRenderer,I as loadLocaleTemplates};
@@ -1 +1 @@
1
- import n from"mjml";import t from"../packem_shared/EmailError-BIaHK1Uw.js";const f=async(a,o,r)=>{try{if(typeof a!="string")throw new TypeError("MJML template must be a string");const e=await n(a,{beautify:r?.beautify??!1,fonts:r?.fonts,keepComments:r?.keepComments??!0,minify:r?.minify??!1,validationLevel:r?.validationLevel??"soft"});if(e.errors&&e.errors.length>0){const s=e.errors.map(m=>m.message).join("; ");throw new t("mjml",`MJML validation errors: ${s}`)}return e.html}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new t("mjml","MJML is not installed. Please install it: pnpm add mjml",{cause:e}):e instanceof t?e:new t("mjml",`Failed to render MJML: ${e.message}`,{cause:e})}};export{f as default};
1
+ import n from"mjml";import t from"../packem_shared/EmailError-SXEFQEyA.js";const f=async(a,o,r)=>{try{if(typeof a!="string")throw new TypeError("MJML template must be a string");const e=await n(a,{beautify:r?.beautify??!1,fonts:r?.fonts,keepComments:r?.keepComments??!0,minify:r?.minify??!1,validationLevel:r?.validationLevel??"soft"});if(e.errors&&e.errors.length>0){const s=e.errors.map(m=>m.message).join("; ");throw new t("mjml",`MJML validation errors: ${s}`)}return e.html}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new t("mjml","MJML is not installed. Please install it: pnpm add mjml",{cause:e}):e instanceof t?e:new t("mjml",`Failed to render MJML: ${e.message}`,{cause:e})}};export{f as default};
@@ -1 +1 @@
1
- import{render as a}from"@react-email/render";import n from"../packem_shared/EmailError-BIaHK1Uw.js";const m=async(i,o,r)=>{try{const e=i,t=r?.pretty;return await(r?.plainText===!0?a(e,{plainText:!0,pretty:t}):a(e,{pretty:t}))}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new n("react-email","@react-email/render is not installed. Please install it: pnpm add @react-email/render",{cause:e}):new n("react-email",`Failed to render React Email component: ${e.message}`,{cause:e})}};export{m as default};
1
+ import{render as a}from"@react-email/render";import n from"../packem_shared/EmailError-SXEFQEyA.js";const m=async(i,o,r)=>{try{const e=i,t=r?.pretty;return await(r?.plainText===!0?a(e,{plainText:!0,pretty:t}):a(e,{pretty:t}))}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new n("react-email","@react-email/render is not installed. Please install it: pnpm add @react-email/render",{cause:e}):new n("react-email",`Failed to render React Email component: ${e.message}`,{cause:e})}};export{m as default};
@@ -1 +1 @@
1
- import{render as i}from"@vue-email/render";import t from"../packem_shared/EmailError-BIaHK1Uw.js";const o=async(a,n,r)=>{try{return await i(a,n??{},{htmlToTextOptions:r?.htmlToTextOptions,plainText:r?.plainText,pretty:r?.pretty})}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new t("vue-email","@vue-email/render is not installed. Please install it: pnpm add @vue-email/render",{cause:e}):new t("vue-email",`Failed to render Vue Email component: ${e.message}`,{cause:e})}};export{o as default};
1
+ import{render as i}from"@vue-email/render";import t from"../packem_shared/EmailError-SXEFQEyA.js";const o=async(a,n,r)=>{try{return await i(a,n??{},{htmlToTextOptions:r?.htmlToTextOptions,plainText:r?.plainText,pretty:r?.pretty})}catch(e){throw e instanceof Error&&e.message.includes("Cannot find module")?new t("vue-email","@vue-email/render is not installed. Please install it: pnpm add @vue-email/render",{cause:e}):new t("vue-email",`Failed to render Vue Email component: ${e.message}`,{cause:e})}};export{o as default};
@@ -1,5 +1,5 @@
1
- import { a as MockEmailEntry } from "../packem_shared/types.d-C5geVKes.js";
2
1
  import { E as EmailOptions } from "../packem_shared/types.d-B9295M4_.js";
2
+ import { a as MockEmailEntry } from "../packem_shared/types.d-C5geVKes.js";
3
3
  import { M as Mail } from "../packem_shared/mail.d-BdyyyQO5.js";
4
4
  import 'node:buffer';
5
5
  import 'ical-generator';
@@ -7,6 +7,23 @@ import "../packem_shared/types.d-I-KeZ5w2.js";
7
7
  import "../packem_shared/types.d-Cb5fls_X.js";
8
8
  import "../packem_shared/types.d-GKqPNk0I.js";
9
9
  import "../packem_shared/provider.d-Cx5YQJdP.js";
10
+ /**
11
+ * Returns the http(s) URLs in a message without duplicates, in this order: `href` targets of
12
+ * anchor and area elements in the HTML, bare URLs in the visible HTML text, then bare URLs in the
13
+ * plain text.
14
+ *
15
+ * Images, `link` and `base` elements, `script` and `style` contents, comments, and CSS `url()`
16
+ * values are not links, so they are not returned. Entities are decoded in HTML only. Only absolute
17
+ * http and https URLs are returned.
18
+ * @param source A plain-text string, or an object with the message's `html` and/or `text`. Pass
19
+ * markup as `{ html }`; a bare string is always treated as plain text.
20
+ * @returns The matching URLs.
21
+ * @example
22
+ * ```ts
23
+ * const [verifyUrl] = extractLinks(email.sent()[0]!.options);
24
+ * ```
25
+ */
26
+ declare const extractLinks: (source: string | Pick<EmailOptions, "html" | "text">) => string[];
10
27
  /**
11
28
  * A test harness around the in-memory mock provider: a ready-to-use {@link Mail} instance plus
12
29
  * helpers to inspect, wait on, and reset the captured outbox.
@@ -151,4 +168,4 @@ interface EmailSnapshot {
151
168
  * ```
152
169
  */
153
170
  declare const toEmailSnapshot: (source: EmailOptions | MockEmailEntry) => EmailSnapshot;
154
- export { type EmailSnapshot, type MatcherResult, type MatcherTarget, type TestEmail, createTestEmail, emailMatchers, registerEmailMatchers, toEmailSnapshot, toHaveSentMatching, toHaveSentTo, toHaveSentWithAttachment, toHaveSentWithSubject };
171
+ export { type EmailSnapshot, type MatcherResult, type MatcherTarget, type TestEmail, createTestEmail, emailMatchers, extractLinks, registerEmailMatchers, toEmailSnapshot, toHaveSentMatching, toHaveSentTo, toHaveSentWithAttachment, toHaveSentWithSubject };
@@ -1 +1 @@
1
- import{emailMatchers as a,registerEmailMatchers as o,toHaveSentMatching as r,toHaveSentTo as i,toHaveSentWithAttachment as m,toHaveSentWithSubject as h}from"../packem_shared/emailMatchers-DpwItmkN.js";import{toEmailSnapshot as c}from"../packem_shared/toEmailSnapshot-BOhTn44E.js";import{createTestEmail as s}from"../packem_shared/createTestEmail-gr512snl.js";export{s as createTestEmail,a as emailMatchers,o as registerEmailMatchers,c as toEmailSnapshot,r as toHaveSentMatching,i as toHaveSentTo,m as toHaveSentWithAttachment,h as toHaveSentWithSubject};
1
+ import{default as a}from"../packem_shared/extractLinks-BwE1SjNF.js";import{emailMatchers as r,registerEmailMatchers as i,toHaveSentMatching as m,toHaveSentTo as n,toHaveSentWithAttachment as c,toHaveSentWithSubject as h}from"../packem_shared/emailMatchers-DpwItmkN.js";import{toEmailSnapshot as S}from"../packem_shared/toEmailSnapshot-BOhTn44E.js";import{createTestEmail as l}from"../packem_shared/createTestEmail-DWimjf0F.js";export{l as createTestEmail,r as emailMatchers,a as extractLinks,i as registerEmailMatchers,S as toEmailSnapshot,m as toHaveSentMatching,n as toHaveSentTo,c as toHaveSentWithAttachment,h as toHaveSentWithSubject};
@@ -1 +1 @@
1
- import{s as a}from"../packem_shared/format-address-Bb7xk8G_.js";import e from"../packem_shared/EmailError-BIaHK1Uw.js";import t from"./validation/validate-email.js";const n=i=>{if(!t(i.email))throw new e("email",`Invalid email address: ${i.email}`);if(i.name){const m=a(i.name);if(m)return`"${m}" <${i.email}>`}return i.email};export{n as default};
1
+ import{s as a}from"../packem_shared/format-address-Bb7xk8G_.js";import e from"../packem_shared/EmailError-SXEFQEyA.js";import t from"./validation/validate-email.js";const n=i=>{if(!t(i.email))throw new e("email",`Invalid email address: ${i.email}`);if(i.name){const m=a(i.name);if(m)return`"${m}" <${i.email}>`}return i.email};export{n as default};
@@ -1 +1 @@
1
- import c from"../packem_shared/EmailError-BIaHK1Uw.js";const a=r=>r.success,t=r=>!r.success,n=r=>{if(r.success)return r.data;throw r.error instanceof Error?r.error:new c("result","Called unwrap() on a failed result",{cause:r.error})},o=(r,s)=>r.success?r.data:s,u=(r,s)=>r.success?{data:s(r.data),success:!0}:{error:r.error,success:!1},i=async r=>{try{return{data:await r(),success:!0}}catch(s){return{error:s,success:!1}}};export{t as isErr,a as isOk,u as mapOk,i as tryAsync,n as unwrap,o as unwrapOr};
1
+ import c from"../packem_shared/EmailError-SXEFQEyA.js";const a=r=>r.success,t=r=>!r.success,n=r=>{if(r.success)return r.data;throw r.error instanceof Error?r.error:new c("result","Called unwrap() on a failed result",{cause:r.error})},o=(r,s)=>r.success?r.data:s,u=(r,s)=>r.success?{data:s(r.data),success:!0}:{error:r.error,success:!1},i=async r=>{try{return{data:await r(),success:!0}}catch(s){return{error:s,success:!1}}};export{t as isErr,a as isOk,u as mapOk,i as tryAsync,n as unwrap,o as unwrapOr};
@@ -1 +1 @@
1
- import{verifyMailgunWebhook as r}from"../packem_shared/verifyMailgunWebhook-DS4o0kZL.js";import{verifyPostmarkWebhook as f}from"../packem_shared/verifyPostmarkWebhook-pLwWmeOl.js";import{verifySendGridWebhook as n}from"../packem_shared/verifySendGridWebhook-csuRasH3.js";import{isValidSigningCertUrl as d,verifySnsMessage as k}from"../packem_shared/isValidSigningCertUrl-CK1t2a7x.js";import{verifyResendWebhook as s,verifyStandardWebhook as v}from"../packem_shared/verifyResendWebhook-D3GuwWjv.js";export{d as isValidSigningCertUrl,r as verifyMailgunWebhook,f as verifyPostmarkWebhook,s as verifyResendWebhook,n as verifySendGridWebhook,k as verifySnsMessage,v as verifyStandardWebhook};
1
+ import{verifyMailgunWebhook as r}from"../packem_shared/verifyMailgunWebhook-DS4o0kZL.js";import{verifyPostmarkWebhook as f}from"../packem_shared/verifyPostmarkWebhook-pLwWmeOl.js";import{verifySendGridWebhook as n}from"../packem_shared/verifySendGridWebhook-0_3qKQA1.js";import{isValidSigningCertUrl as d,verifySnsMessage as k}from"../packem_shared/isValidSigningCertUrl-gnYr3oyD.js";import{verifyResendWebhook as s,verifyStandardWebhook as v}from"../packem_shared/verifyResendWebhook-D3GuwWjv.js";export{d as isValidSigningCertUrl,r as verifyMailgunWebhook,f as verifyPostmarkWebhook,s as verifyResendWebhook,n as verifySendGridWebhook,k as verifySnsMessage,v as verifyStandardWebhook};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/email",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "A comprehensive email library with multi-provider support, crypto utilities, and template engines",
5
5
  "keywords": [
6
6
  "ahasend",
@@ -122,6 +122,10 @@
122
122
  "types": "./dist/template-engines/i18n.d.ts",
123
123
  "default": "./dist/template-engines/i18n.js"
124
124
  },
125
+ "./template/locale-files": {
126
+ "types": "./dist/template-engines/locale-files.d.ts",
127
+ "default": "./dist/template-engines/locale-files.js"
128
+ },
125
129
  "./providers/autosend": {
126
130
  "types": "./dist/providers/autosend/index.d.ts",
127
131
  "default": "./dist/providers/autosend/index.js"
@@ -158,6 +162,10 @@
158
162
  "types": "./dist/providers/mailcrab/index.d.ts",
159
163
  "default": "./dist/providers/mailcrab/index.js"
160
164
  },
165
+ "./providers/file": {
166
+ "types": "./dist/providers/file/index.d.ts",
167
+ "default": "./dist/providers/file/index.js"
168
+ },
161
169
  "./providers/nodemailer": {
162
170
  "types": "./dist/providers/nodemailer/index.d.ts",
163
171
  "default": "./dist/providers/nodemailer/index.js"
@@ -1 +0,0 @@
1
- class l extends Error{loc;title;hint;type="VisulimaError";constructor({cause:t,hint:e,location:s,message:a,name:c,stack:r,title:h}){super(a,{cause:t}),this.title=h,this.name=c,this.stack=r??this.stack,this.loc=s,this.hint=e}setLocation(t){this.loc=t}setName(t){this.name=t}setMessage(t){this.message=t}setHint(t){this.hint=t}}class o extends l{component;code;constructor(t,e,s){super({cause:s?.cause,hint:s?.hint,message:`[@visulima/email] [${t}] ${e}`,name:"EmailError",title:`Email ${t} Error`}),this.component=t,this.code=s?.code}}export{o as default};
@@ -1 +0,0 @@
1
- const k=n=>1e3*2**(n-1),g=n=>{const{backoff:d=k,concurrency:v=1,maxAttempts:y=3,onDeadLetter:h,onError:u,onReserveError:m,pollInterval:p=1e3,queue:t,send:E}=n;let c=!1,i=0,s;const o=async(r,e)=>{try{await e()}catch(a){u?.(r,{error:a instanceof Error?a:new Error(String(a)),success:!1})}},l=async r=>{let e;try{e=await E(r.message)}catch(f){e={error:f instanceof Error?f:new Error(String(f)),success:!1}}if(e.success){await o(r,()=>t.ack(r.id));return}u?.(r,e);const a=r.attempts+1;if(a>=y){await o(r,()=>t.ack(r.id)),h?.(r,e);return}await o(r,()=>t.retry(r.id,d(a)))},w=async()=>{if(c){try{for(;i<v;){const r=await t.reserve();if(!r)break;i+=1,l(r).finally(()=>{i-=1}).catch(()=>{})}}catch(r){m?.(r instanceof Error?r:new Error(String(r)))}c&&(s=setTimeout(()=>{w().catch(()=>{})},p))}};return{drain:async()=>{let r=await t.reserve();for(;r;)await l(r),r=await t.reserve()},start:()=>{c||(c=!0,w().catch(()=>{}))},stop:()=>{c=!1,s&&(clearTimeout(s),s=void 0)}}};export{g as createWorker};
@@ -1 +0,0 @@
1
- import f from"./EmailError-BIaHK1Uw.js";const w=globalThis.Buffer!==void 0,T="ETIMEDOUT",g=async(i,s={},t)=>{const d=typeof i=="string"?new URL(i):i;try{const r=new Headers;s.headers&&Object.entries(s.headers).forEach(([e,u])=>{r.set(e,u)});const o={headers:r,method:s.method??(t?"POST":"GET")};t&&(typeof t=="string"||t instanceof Uint8Array?o.body=t:w&&t instanceof globalThis.Buffer?o.body=new Uint8Array(t):o.body=new Uint8Array(t));let a,n;s.timeout&&(a=new AbortController,n=setTimeout(()=>{a?.abort()},s.timeout),o.signal=a.signal);try{const e=await fetch(d.toString(),o);n&&clearTimeout(n);const m=(e.headers.get("content-type")??"").includes("application/json");let c;if(m)try{c=await e.json()}catch{c=await e.text()}else c=await e.text();const l=e.status>=200&&e.status<300,h={};return e.headers.forEach((y,E)=>{h[E]=y}),{data:{body:c,headers:h,statusCode:e.status},error:l?void 0:new f("http",`Request failed with status ${e.status}`,{code:e.status.toString()}),success:l}}catch(e){if(n&&clearTimeout(n),e instanceof Error&&e.name==="AbortError")return{error:new f("http",`Request timed out after ${s.timeout}ms`,{code:T}),success:!1};throw e}}catch(r){return{error:new f("http",`Request failed: ${r instanceof Error?r.message:String(r)}`,{cause:r instanceof Error?r:new Error(String(r))}),success:!1}}};export{T as R,g as m};