@visulima/email 1.0.0-alpha.39 → 1.0.0-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## @visulima/email [1.0.0-alpha.40](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.39...@visulima/email@1.0.0-alpha.40) (2026-06-20)
2
+
3
+ ### Features
4
+
5
+ * **email:** add sparkpost, netcore and outlook365 providers ([b8efc45](https://github.com/visulima/visulima/commit/b8efc45e273dffa246a4badc1711f01682ea7af5))
6
+
7
+ ### Tests
8
+
9
+ * **email:** fix ical file-url path assertion on windows ([3673d90](https://github.com/visulima/visulima/commit/3673d901f522430bea634f6c93bd286577fa144f))
10
+
1
11
  ## @visulima/email [1.0.0-alpha.39](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.38...@visulima/email@1.0.0-alpha.39) (2026-06-19)
2
12
 
3
13
  ## @visulima/email [1.0.0-alpha.38](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.37...@visulima/email@1.0.0-alpha.38) (2026-06-19)
package/README.md CHANGED
@@ -912,6 +912,70 @@ const sendGridOptions: SendGridEmailOptions = {
912
912
  await mail.send(sendGridOptions);
913
913
  ```
914
914
 
915
+ ### SparkPost Provider
916
+
917
+ SparkPost is a high-volume email delivery service. Universal runtime (Fetch API).
918
+
919
+ ```typescript
920
+ import { createMail, sparkpostProvider } from "@visulima/email/providers/sparkpost";
921
+
922
+ const mail = createMail(
923
+ sparkpostProvider({
924
+ apiKey: "your-sparkpost-api-key",
925
+ endpoint: "https://api.sparkpost.com/api/v1", // or https://api.eu.sparkpost.com/api/v1 (EU)
926
+ }),
927
+ );
928
+
929
+ await mail.send({
930
+ from: { email: "sender@example.com" },
931
+ to: { email: "user@example.com" },
932
+ subject: "Welcome",
933
+ html: "<h1>Welcome!</h1>",
934
+ // SparkPost-specific: campaignId, templateId, trackOpens, trackClicks
935
+ });
936
+ ```
937
+
938
+ ### Netcore Provider
939
+
940
+ Netcore (formerly Pepipost) transactional email via the Email API v5.1. Universal runtime (Fetch API).
941
+
942
+ ```typescript
943
+ import { createMail, netcoreProvider } from "@visulima/email/providers/netcore";
944
+
945
+ const mail = createMail(netcoreProvider({ apiKey: "your-netcore-api-key" }));
946
+
947
+ await mail.send({
948
+ from: { email: "sender@example.com" },
949
+ to: { email: "user@example.com" },
950
+ subject: "Welcome",
951
+ html: "<h1>Welcome!</h1>",
952
+ // Netcore-specific: templateId, templateData
953
+ });
954
+ ```
955
+
956
+ ### Outlook365 Provider
957
+
958
+ Sends through Microsoft Graph `sendMail`. Universal runtime (Fetch API). Bring your own OAuth2 token (`Mail.Send` scope)
959
+ via `accessToken` or `getAccessToken` — no auth SDK is bundled.
960
+
961
+ ```typescript
962
+ import { createMail, outlook365Provider } from "@visulima/email/providers/outlook365";
963
+
964
+ const mail = createMail(
965
+ outlook365Provider({
966
+ getAccessToken: async () => getGraphAccessToken(),
967
+ userId: "sender@contoso.com", // or "me" (default)
968
+ }),
969
+ );
970
+
971
+ await mail.send({
972
+ from: { email: "sender@contoso.com" },
973
+ to: { email: "user@example.com" },
974
+ subject: "Welcome",
975
+ html: "<h1>Welcome!</h1>",
976
+ });
977
+ ```
978
+
915
979
  ### Plunk Provider
916
980
 
917
981
  Plunk is a modern email platform built on top of AWS SES, offering transactional emails, automations, and broadcasts.
@@ -1069,6 +1133,9 @@ const result = await mail.send(message);
1069
1133
  - **Round Robin** - Load balancing across multiple providers
1070
1134
  - **OpenTelemetry** - OpenTelemetry instrumentation wrapper for observability
1071
1135
  - **SendGrid** - Cloud-based email service for transactional and marketing emails
1136
+ - **SparkPost** - SparkPost Transmissions API
1137
+ - **Netcore** - Netcore (Pepipost) Email API
1138
+ - **Outlook365** - Microsoft Graph (Outlook365) `sendMail`
1072
1139
  - **SMTP** - Standard SMTP protocol
1073
1140
  - **Zeptomail** - Zeptomail email service
1074
1141
 
@@ -1099,6 +1166,9 @@ These providers work in **Node.js**, **Deno**, **Bun**, and **Cloudflare Workers
1099
1166
  - ✅ **Mailomat** - Uses Fetch API
1100
1167
  - ✅ **Sweego** - Uses Fetch API
1101
1168
  - ✅ **SendGrid** - Uses Fetch API
1169
+ - ✅ **SparkPost** - Uses Fetch API
1170
+ - ✅ **Netcore** - Uses Fetch API
1171
+ - ✅ **Outlook365** - Uses Fetch API (Microsoft Graph; bring your own OAuth2 token)
1102
1172
  - ✅ **Plunk** - Uses Fetch API
1103
1173
  - ✅ **Mock** - In-memory provider, works everywhere
1104
1174
  - ✅ **Failover** - Runtime depends on wrapped providers (works if all wrapped providers support the runtime)
@@ -0,0 +1 @@
1
+ import p from"./EmailError-B-sEf3bp.js";import g from"./RequiredOptionError-DuNNY_a5.js";import{e as y}from"./generate-message-id-gXAuMz4g.js";import{m as v}from"./make-request-BlK_B72c.js";import{u as b}from"./retry-3pnxTuNs.js";import{f as w}from"./validate-email-options-mCM9dVug.js";import{defineProvider as P}from"./defineProvider-C9q-7iwt.js";import{P as I,h as A,c as z}from"./provider-base-gIoUu9so.js";import{a as l}from"./address-formatter-BWR2Dddx.js";import{c as K}from"./attachment-processor-BjTNoMyb.js";const a="netcore",S="https://emailapi.netcorecloud.net/v5.1",T=3e4,_=3,m=r=>[r].flat(),F=P((r={})=>{if(!r.apiKey)throw new g(a,"apiKey");const i={apiKey:r.apiKey,debug:r.debug??!1,endpoint:r.endpoint??S,logger:r.logger,retries:r.retries??_,timeout:r.timeout??T},d=new I,u=z(a,r.logger);return{features:{attachments:!0,batchSending:!0,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!0,templates:!0,tracking:!0},async initialize(){await d.ensureInitialized(async()=>{if(!await this.isAvailable())throw new p(a,"Netcore API not available or invalid API key");u.debug("Provider initialized successfully")},a)},async isAvailable(){return i.apiKey.length>0},name:a,options:i,async sendEmail(t){try{const n=w(t);if(n.length>0)return{error:new p(a,`Invalid email options: ${n.join(", ")}`),success:!1};await d.ensureInitialized(()=>this.initialize(),a);const o={to:m(t.to).map(e=>l(e))};t.cc&&(o.cc=m(t.cc).map(e=>l(e))),t.bcc&&(o.bcc=m(t.bcc).map(e=>l(e)));const s={from:l(t.from),personalizations:[o],subject:t.subject};if(t.templateId)s.template_id=t.templateId,t.templateData&&(o.attributes=t.templateData);else{const e=[];t.html&&e.push({type:"html",value:t.html}),t.text&&e.push({type:"plain",value:t.text}),e.length>0&&(s.content=e)}if(t.replyTo){const e=m(t.replyTo)[0];e&&(s.reply_to=e.email)}t.attachments&&t.attachments.length>0&&(s.attachments=await Promise.all(t.attachments.map(async e=>{const h=await K(e,a);return{content:h.content,name:h.filename}})));const c=await b(async()=>v(`${i.endpoint}/mail/send`,{headers:{api_key:i.apiKey,"Content-Type":"application/json"},method:"POST",timeout:i.timeout},JSON.stringify(s)),i.retries);if(!c.success)return{error:c.error??new p(a,"Failed to send email"),success:!1};const{body:f}=c.data;return{data:{messageId:f?.data?.message_id??f?.message_id??y(),provider:a,response:c.data,sent:!0,timestamp:new Date},success:!0}}catch(n){return{error:A(a,"send email",n,u),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{F as default};
@@ -0,0 +1 @@
1
+ import u from"./EmailError-B-sEf3bp.js";import h from"./RequiredOptionError-DuNNY_a5.js";import{e as y}from"./generate-message-id-gXAuMz4g.js";import{m as k}from"./make-request-BlK_B72c.js";import{u as v}from"./retry-3pnxTuNs.js";import{f as A}from"./validate-email-options-mCM9dVug.js";import{defineProvider as b}from"./defineProvider-C9q-7iwt.js";import{c as w}from"./attachment-processor-BjTNoMyb.js";import{h as I,P as S,c as P}from"./provider-base-gIoUu9so.js";const a="outlook365",j="https://graph.microsoft.com/v1.0",R=3e4,x=3,o=t=>[t].flat(),c=t=>t.map(s=>({emailAddress:{address:s.email,...s.name?{name:s.name}:{}}})),q=b((t={})=>{if(!t.accessToken&&!t.getAccessToken)throw new h(a,["accessToken","getAccessToken"]);const s={accessToken:t.accessToken,debug:t.debug??!1,endpoint:t.endpoint??j,getAccessToken:t.getAccessToken,logger:t.logger,retries:t.retries??x,saveToSentItems:t.saveToSentItems??!0,timeout:t.timeout??R,userId:t.userId??"me"},p=new S,f=P(a,t.logger),g=async()=>s.getAccessToken?s.getAccessToken():s.accessToken;return{features:{attachments:!0,batchSending:!1,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!1,templates:!1,tracking:!1},async initialize(){p.setInitialized()},async isAvailable(){return!!(s.accessToken??s.getAccessToken)},name:a,options:s,async sendEmail(e){try{const n=A(e);if(n.length>0)return{error:new u(a,`Invalid email options: ${n.join(", ")}`),success:!1};let d;try{d=await g()}catch(m){return{error:new u(a,"Failed to obtain access token",{cause:m}),success:!1}}const r={body:{content:e.html??e.text??"",contentType:e.html?"HTML":"Text"},subject:e.subject,toRecipients:c(o(e.to))};e.cc&&(r.ccRecipients=c(o(e.cc))),e.bcc&&(r.bccRecipients=c(o(e.bcc))),e.replyTo&&(r.replyTo=c(o(e.replyTo))),e.importance&&(r.importance=e.importance),e.attachments&&e.attachments.length>0&&(r.attachments=await Promise.all(e.attachments.map(async m=>{const l=await w(m,a);return{"@odata.type":"#microsoft.graph.fileAttachment",contentBytes:l.content,contentType:l.contentType,name:l.filename}})));const T=s.userId==="me"?"me":`users/${s.userId}`,i=await v(async()=>k(`${s.endpoint}/${T}/sendMail`,{headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},method:"POST",timeout:s.timeout},JSON.stringify({message:r,saveToSentItems:s.saveToSentItems})),s.retries);return i.success?{data:{messageId:y(),provider:a,response:i.data,sent:!0,timestamp:new Date},success:!0}:{error:i.error??new u(a,"Failed to send email"),success:!1}}catch(n){return{error:I(a,"send email",n,f),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{q as default};
@@ -0,0 +1 @@
1
+ import h from"./EmailError-B-sEf3bp.js";import w from"./RequiredOptionError-DuNNY_a5.js";import{e as P}from"./generate-message-id-gXAuMz4g.js";import{m as j}from"./make-request-BlK_B72c.js";import{u as I}from"./retry-3pnxTuNs.js";import{f as A}from"./validate-email-options-mCM9dVug.js";import{defineProvider as S}from"./defineProvider-C9q-7iwt.js";import{P as z,h as _,c as C}from"./provider-base-gIoUu9so.js";import{a as l}from"./address-formatter-BWR2Dddx.js";import{c as K}from"./attachment-processor-BjTNoMyb.js";const a="sparkpost",O="https://api.sparkpost.com/api/v1",T=3e4,x=3,d=r=>[r].flat(),B=S((r={})=>{if(!r.apiKey)throw new w(a,"apiKey");const n={apiKey:r.apiKey,debug:r.debug??!1,endpoint:r.endpoint??O,logger:r.logger,retries:r.retries??x,timeout:r.timeout??T},g=new z,y=C(a,r.logger);return{features:{attachments:!0,batchSending:!0,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!0,templates:!0,tracking:!0},async initialize(){await g.ensureInitialized(async()=>{if(!await this.isAvailable())throw new h(a,"SparkPost API not available or invalid API key");y.debug("Provider initialized successfully")},a)},async isAvailable(){return n.apiKey.length>0},name:a,options:n,async sendEmail(t){try{const o=A(t);if(o.length>0)return{error:new h(a,`Invalid email options: ${o.join(", ")}`),success:!1};await g.ensureInitialized(()=>this.initialize(),a);const p=d(t.to),u=t.cc?d(t.cc):[],k=t.bcc?d(t.bcc):[],v=[...p.map(e=>({address:l(e)})),...u.map(e=>({address:{...l(e),header_to:p.map(i=>i.email).join(", ")}})),...k.map(e=>({address:{...l(e),header_to:p.map(i=>i.email).join(", ")}}))],s={from:l(t.from),subject:t.subject};if(t.html&&(s.html=t.html),t.text&&(s.text=t.text),t.replyTo){const e=d(t.replyTo)[0];e&&(s.reply_to=e.email)}const c={};if(u.length>0&&(c.CC=u.map(e=>e.email).join(", ")),t.headers)for(const[e,i]of Object.entries(t.headers))c[e]=String(i);Object.keys(c).length>0&&(s.headers=c),t.templateId&&(s.template_id=t.templateId),t.attachments&&t.attachments.length>0&&(s.attachments=await Promise.all(t.attachments.map(async e=>{const i=await K(e,a);return{data:i.content,name:i.filename,type:i.contentType}})));const f={content:s,recipients:v};t.campaignId&&(f.campaign_id=t.campaignId),(t.trackOpens!==void 0||t.trackClicks!==void 0)&&(f.options={click_tracking:t.trackClicks??!0,open_tracking:t.trackOpens??!0});const m=await I(async()=>j(`${n.endpoint}/transmissions`,{headers:{Authorization:n.apiKey,"Content-Type":"application/json"},method:"POST",timeout:n.timeout},JSON.stringify(f)),n.retries);if(!m.success)return{error:m.error??new h(a,"Failed to send email"),success:!1};const{body:b}=m.data;return{data:{messageId:b?.results?.id??P(),provider:a,response:m.data,sent:!0,timestamp:new Date},success:!0}}catch(o){return{error:_(a,"send email",o,y),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{B as default};
@@ -0,0 +1,35 @@
1
+ import { P as ProviderFactory } from "../../packem_shared/provider.d-J-y6iogK.js";
2
+ export type { a as Provider } from "../../packem_shared/provider.d-J-y6iogK.js";
3
+ import { B as BaseConfig, E as EmailOptions } from "../../packem_shared/types.d-ejzh-xBP.js";
4
+ import 'node:buffer';
5
+ /**
6
+ * Netcore (Pepipost) configuration.
7
+ */
8
+ interface NetcoreConfig extends BaseConfig {
9
+ /**
10
+ * Netcore Email API key (sent as the `api_key` header).
11
+ */
12
+ apiKey: string;
13
+ /**
14
+ * API endpoint override.
15
+ */
16
+ endpoint?: string;
17
+ }
18
+ /**
19
+ * Netcore-specific email options.
20
+ */
21
+ interface NetcoreEmailOptions extends EmailOptions {
22
+ /**
23
+ * Variables substituted into the Netcore template.
24
+ */
25
+ templateData?: Record<string, unknown>;
26
+ /**
27
+ * Stored template id to send instead of inline content.
28
+ */
29
+ templateId?: string;
30
+ }
31
+ /**
32
+ * Netcore (Pepipost) provider — sends email through the Netcore Email API (v5.1).
33
+ */
34
+ declare const netcoreProvider: ProviderFactory<NetcoreConfig>;
35
+ export { type NetcoreConfig, type NetcoreEmailOptions, type ProviderFactory, netcoreProvider };
@@ -0,0 +1 @@
1
+ import{default as o}from"../../packem_shared/netcoreProvider-XMWO_Aoa.js";export{o as netcoreProvider};
@@ -0,0 +1,48 @@
1
+ import { P as ProviderFactory } from "../../packem_shared/provider.d-J-y6iogK.js";
2
+ export type { a as Provider } from "../../packem_shared/provider.d-J-y6iogK.js";
3
+ import { M as MaybePromise, B as BaseConfig, E as EmailOptions } from "../../packem_shared/types.d-ejzh-xBP.js";
4
+ import 'node:buffer';
5
+ /**
6
+ * Outlook365 / Microsoft Graph configuration.
7
+ *
8
+ * Authentication is delegated: supply a static `accessToken` or an async
9
+ * `getAccessToken` (e.g. backed by `@azure/msal-node`). The provider never bundles an
10
+ * auth SDK, so it stays dependency-light and runtime-agnostic.
11
+ */
12
+ interface Outlook365Config extends BaseConfig {
13
+ /**
14
+ * A static OAuth2 access token with the `Mail.Send` scope. Prefer
15
+ * {@link Outlook365Config.getAccessToken} for tokens that expire.
16
+ */
17
+ accessToken?: string;
18
+ /**
19
+ * API endpoint override (default `https://graph.microsoft.com/v1.0`).
20
+ */
21
+ endpoint?: string;
22
+ /**
23
+ * Returns a fresh OAuth2 access token with the `Mail.Send` scope.
24
+ */
25
+ getAccessToken?: () => MaybePromise<string>;
26
+ /**
27
+ * Whether to keep a copy in the Sent Items folder (default true).
28
+ */
29
+ saveToSentItems?: boolean;
30
+ /**
31
+ * The mailbox to send as: a user id/UPN. Defaults to `me` (the token's own mailbox).
32
+ */
33
+ userId?: string;
34
+ }
35
+ /**
36
+ * Outlook365-specific email options.
37
+ */
38
+ interface Outlook365EmailOptions extends EmailOptions {
39
+ /**
40
+ * Message importance.
41
+ */
42
+ importance?: "high" | "low" | "normal";
43
+ }
44
+ /**
45
+ * Outlook365 provider — sends email through the Microsoft Graph `sendMail` endpoint.
46
+ */
47
+ declare const outlook365Provider: ProviderFactory<Outlook365Config>;
48
+ export { type Outlook365Config, type Outlook365EmailOptions, type ProviderFactory, outlook365Provider };
@@ -0,0 +1 @@
1
+ import{default as e}from"../../packem_shared/outlook365Provider-BoNtxqIx.js";export{e as outlook365Provider};
@@ -0,0 +1,43 @@
1
+ import { P as ProviderFactory } from "../../packem_shared/provider.d-J-y6iogK.js";
2
+ export type { a as Provider } from "../../packem_shared/provider.d-J-y6iogK.js";
3
+ import { B as BaseConfig, E as EmailOptions } from "../../packem_shared/types.d-ejzh-xBP.js";
4
+ import 'node:buffer';
5
+ /**
6
+ * SparkPost configuration.
7
+ */
8
+ interface SparkPostConfig extends BaseConfig {
9
+ /**
10
+ * SparkPost API key (sent as the `Authorization` header).
11
+ */
12
+ apiKey: string;
13
+ /**
14
+ * API endpoint override. Use `https://api.eu.sparkpost.com/api/v1` for the EU region.
15
+ */
16
+ endpoint?: string;
17
+ }
18
+ /**
19
+ * SparkPost-specific email options.
20
+ */
21
+ interface SparkPostEmailOptions extends EmailOptions {
22
+ /**
23
+ * Campaign id applied to the transmission.
24
+ */
25
+ campaignId?: string;
26
+ /**
27
+ * Stored template id to send instead of inline content.
28
+ */
29
+ templateId?: string;
30
+ /**
31
+ * Whether click tracking is enabled.
32
+ */
33
+ trackClicks?: boolean;
34
+ /**
35
+ * Whether engagement tracking (opens/clicks) is enabled.
36
+ */
37
+ trackOpens?: boolean;
38
+ }
39
+ /**
40
+ * SparkPost provider — sends email through the SparkPost Transmissions API.
41
+ */
42
+ declare const sparkpostProvider: ProviderFactory<SparkPostConfig>;
43
+ export { type ProviderFactory, type SparkPostConfig, type SparkPostEmailOptions, sparkpostProvider };
@@ -0,0 +1 @@
1
+ import{default as a}from"../../packem_shared/sparkpostProvider-BytvwqjP.js";export{a as sparkpostProvider};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/email",
3
- "version": "1.0.0-alpha.39",
3
+ "version": "1.0.0-alpha.40",
4
4
  "description": "A comprehensive email library with multi-provider support, crypto utilities, and template engines",
5
5
  "keywords": [
6
6
  "ahasend",
@@ -42,11 +42,14 @@
42
42
  "mandrill",
43
43
  "mjml",
44
44
  "mock",
45
+ "netcore",
45
46
  "nodemailer",
46
47
  "opentelemetry",
48
+ "outlook365",
47
49
  "plunk",
48
50
  "postal",
49
51
  "postmark",
52
+ "sparkpost",
50
53
  "react-email",
51
54
  "resend",
52
55
  "roundrobin",
@@ -301,6 +304,18 @@
301
304
  "types": "./dist/providers/loops/index.d.ts",
302
305
  "default": "./dist/providers/loops/index.js"
303
306
  },
307
+ "./providers/sparkpost": {
308
+ "types": "./dist/providers/sparkpost/index.d.ts",
309
+ "default": "./dist/providers/sparkpost/index.js"
310
+ },
311
+ "./providers/netcore": {
312
+ "types": "./dist/providers/netcore/index.d.ts",
313
+ "default": "./dist/providers/netcore/index.js"
314
+ },
315
+ "./providers/outlook365": {
316
+ "types": "./dist/providers/outlook365/index.d.ts",
317
+ "default": "./dist/providers/outlook365/index.js"
318
+ },
304
319
  "./utils/parse-address": {
305
320
  "types": "./dist/utils/parse-address.d.ts",
306
321
  "default": "./dist/utils/parse-address.js"