@visulima/email 1.0.0-alpha.32 → 1.0.0-alpha.34

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,137 @@
1
+ ## @visulima/email [1.0.0-alpha.34](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.33...@visulima/email@1.0.0-alpha.34) (2026-06-02)
2
+
3
+ ### Features
4
+
5
+ * **email:** add fail-fast capability guard ([#657](https://github.com/visulima/visulima/issues/657)) ([81f24ab](https://github.com/visulima/visulima/commit/81f24abcde5cb8b6bbbd989a5ad397eefb3292ab))
6
+
7
+ ### Bug Fixes
8
+
9
+ * **tests:** revert unsafe vitest autofixes from the lint sweep ([378f27c](https://github.com/visulima/visulima/commit/378f27caa370f1d3188aef2ed36d46839abc88c4))
10
+
11
+ ### Miscellaneous Chores
12
+
13
+ * apply eslint + prettier autofixes across packages ([c1bb784](https://github.com/visulima/visulima/commit/c1bb7848a0d93d0dfe2960c77e3cda22239c79a0))
14
+
15
+ ### Tests
16
+
17
+ * **email:** cover address normalization branches ([128f9d4](https://github.com/visulima/visulima/commit/128f9d485b0007636cf369f0c9a435c50d445794))
18
+ * **email:** cover ahasend request, attachment and availability branches ([747ba3c](https://github.com/visulima/visulima/commit/747ba3cef6b84cd79d82b8441cb250032e9247eb))
19
+ * **email:** cover ahasend send and messageId branches ([b9c5085](https://github.com/visulima/visulima/commit/b9c5085546f2abad3515d9d899a7f40f2b12cb58))
20
+ * **email:** cover angle-bracket first-bracket fallback ([3f6f8b9](https://github.com/visulima/visulima/commit/3f6f8b9c6d44d370140d59146d8446be3a5b035a))
21
+ * **email:** cover attach/embed empty basename fallback ([b5be774](https://github.com/visulima/visulima/commit/b5be7745cfeed21ec92911f1673c712744f2f311))
22
+ * **email:** cover attachment content-type fallbacks ([94bf172](https://github.com/visulima/visulima/commit/94bf172278f65af5c3e4260ae2aeaf7ed35ca67a))
23
+ * **email:** cover attachment default content type ([ac83c50](https://github.com/visulima/visulima/commit/ac83c501a40a874e721d9a58e5e5b0fd6125795e))
24
+ * **email:** cover aws-ses branch paths ([c299c28](https://github.com/visulima/visulima/commit/c299c2873fcc309ce2c6695fb148dfab36bf795d))
25
+ * **email:** cover aws-ses initialize, isAvailable, request errors and SES params ([b0c90c7](https://github.com/visulima/visulima/commit/b0c90c7245d3a68d891e1673e7acf8d6a9f129d7))
26
+ * **email:** cover azure availability non-error branch ([1fea2cb](https://github.com/visulima/visulima/commit/1fea2cb1bd6908ef87b2de109b7e7159015f30ad))
27
+ * **email:** cover azure connection-string auth and send/getEmail branches ([462d4f9](https://github.com/visulima/visulima/commit/462d4f95bb8d59fd459b1cd812640028abf60237))
28
+ * **email:** cover azure getEmail auth, isAvailable status codes and send failure ([d03fe62](https://github.com/visulima/visulima/commit/d03fe62ceffce0598af844453e40daa64817bb0a))
29
+ * **email:** cover brevo getEmail, replyTo array branches, batch and attachments ([e967213](https://github.com/visulima/visulima/commit/e967213b93e24075d62bd6e12e43015fed045dda))
30
+ * **email:** cover brevo send and getEmail branches ([78389b9](https://github.com/visulima/visulima/commit/78389b90d0bf19e89e6e58ecdb07887a1e8618c4))
31
+ * **email:** cover build-mime-message attachment headers and encodings ([1095d47](https://github.com/visulima/visulima/commit/1095d471f8ed38755b3a847f3b39fdb3e7621722))
32
+ * **email:** cover dkim empty-body canonicalization ([b5a7f4c](https://github.com/visulima/visulima/commit/b5a7f4c24ca211d0ae76d8df9a9f667e890cfcc0))
33
+ * **email:** cover dkim-signer canonicalization branches ([d6c0bec](https://github.com/visulima/visulima/commit/d6c0bece326173678650f5a4f30078e35e17cffb))
34
+ * **email:** cover failover init failures and fallbacks ([2cbead7](https://github.com/visulima/visulima/commit/2cbead75a564c25d3f921e80f49b099595927628))
35
+ * **email:** cover failover unnamed-provider init failure ([38e0060](https://github.com/visulima/visulima/commit/38e0060f3cdfc963b6a85d725b7519b0fa188fb6))
36
+ * **email:** cover handlebars register error rethrow ([b740f8f](https://github.com/visulima/visulima/commit/b740f8f2ed121a953cb5e02f81eebef4f172b598))
37
+ * **email:** cover http initialize, availability, and cc/bcc branches ([b2f6509](https://github.com/visulima/visulima/commit/b2f6509c67fd3ba5e4b4e20be8c48965fa0e1928))
38
+ * **email:** cover http send, init, and message-id branches ([62719bd](https://github.com/visulima/visulima/commit/62719bd9e9e9dece8de1e26b44854c73923b4cf8))
39
+ * **email:** cover in-memory cache delete, clear and eviction ([e4b0333](https://github.com/visulima/visulima/commit/e4b03330889d2f5e822fa61b201411991c19d498))
40
+ * **email:** cover infobip request, attachment and availability branches ([2dac360](https://github.com/visulima/visulima/commit/2dac3608200f75f7f795accadf226f242937831b))
41
+ * **email:** cover infobip send and getEmail branches ([4d8719d](https://github.com/visulima/visulima/commit/4d8719db845882f0c52d580b47f040e5533fbae8))
42
+ * **email:** cover Mail logger, sendMany, and global-config merge paths ([bd5c50b](https://github.com/visulima/visulima/commit/bd5c50be8b6aa110086f0c9975440decf1189ab6))
43
+ * **email:** cover mail message replyTo setter ([afe8dae](https://github.com/visulima/visulima/commit/afe8daed7363d7fdd7354b8e3190602fdc635347))
44
+ * **email:** cover mail send and batch branch paths ([b45b1b0](https://github.com/visulima/visulima/commit/b45b1b0e0066a276bee5a6bfddde13a63f7f415b))
45
+ * **email:** cover mail-message auto-text generation ([2c44d7f](https://github.com/visulima/visulima/commit/2c44d7fa6636ca4c88e38504aa988855f6387436))
46
+ * **email:** cover mail-message logger branches ([23b4496](https://github.com/visulima/visulima/commit/23b44963ed8edf4e794544bdcde94f2d3798994a))
47
+ * **email:** cover mailersend request, attachment and availability branches ([999e22c](https://github.com/visulima/visulima/commit/999e22c94828e382f962d3bb695f2a63bc3e985b))
48
+ * **email:** cover mailersend send and getEmail branches ([e20e805](https://github.com/visulima/visulima/commit/e20e8055fc8058a471fd45fb1526a6e35504865e))
49
+ * **email:** cover mailgun getEmail events, tracking flags, form encoding and attachments ([cf63a01](https://github.com/visulima/visulima/commit/cf63a013cdcfc546fb731ad7c9b381b637490bd5))
50
+ * **email:** cover mailgun send and getEmail branches ([3f63011](https://github.com/visulima/visulima/commit/3f63011a43752e8ba4f19e11857843b1f4830557))
51
+ * **email:** cover mailjet getEmail, send failure, campaign fields and attachments ([2f4147f](https://github.com/visulima/visulima/commit/2f4147ff105386ac13700154a2fb5e081fa6d70b))
52
+ * **email:** cover mailjet send and getEmail branches ([d692d34](https://github.com/visulima/visulima/commit/d692d346dc179f9778857d502824b3f68cd0f769))
53
+ * **email:** cover mailomat and mailpace getEmail and edge paths ([519d057](https://github.com/visulima/visulima/commit/519d057b19f23f2157c379595fa041fb17fff189))
54
+ * **email:** cover mailomat getEmail and branch paths ([8b52328](https://github.com/visulima/visulima/commit/8b52328146ccb0f6f2a10ce4652679e13debdb1a))
55
+ * **email:** cover mailomat send/getEmail branch paths ([df5b5a4](https://github.com/visulima/visulima/commit/df5b5a446c8f373d09f5cab3d98280b35d495657))
56
+ * **email:** cover mailpace availability and send branches ([b89b4ad](https://github.com/visulima/visulima/commit/b89b4ad51cde34d58656944a171434d6b2d361d7))
57
+ * **email:** cover mailpace logger branch ([8e4fe77](https://github.com/visulima/visulima/commit/8e4fe779599f5ad43a63c24f7e7e9fa4d5b631ce))
58
+ * **email:** cover mailtrap request, attachment and availability branches ([ee23353](https://github.com/visulima/visulima/commit/ee2335365cf6aac08578e7010b20752768bc3c8d))
59
+ * **email:** cover mailtrap send and getEmail branches ([be3e1a3](https://github.com/visulima/visulima/commit/be3e1a3b85fc49c7f9848d3d5f12b1aa2df18ab8))
60
+ * **email:** cover make-request non-error rejection ([9626626](https://github.com/visulima/visulima/commit/9626626f57afca5b8dcbb1653d9796575a17d666))
61
+ * **email:** cover make-request timeout cleanup, json fallback and body coercion ([3d76d4e](https://github.com/visulima/visulima/commit/3d76d4ee92da4159ea9a5c6d247ee22f680ed3a4))
62
+ * **email:** cover mandrill request, analytics and availability branches ([1233ff3](https://github.com/visulima/visulima/commit/1233ff327f34246ea2363a0b914e88d7c8471d78))
63
+ * **email:** cover mandrill send and template branches ([297754f](https://github.com/visulima/visulima/commit/297754f8aa984269da98a082e8252b4cca9f1fc2))
64
+ * **email:** cover mock init, recipient, and response branches ([5b301f5](https://github.com/visulima/visulima/commit/5b301f5d8de4bc0778379d9b2da8024913f2bcc2))
65
+ * **email:** cover mock provider send/retrieve catch paths ([bb47683](https://github.com/visulima/visulima/commit/bb47683295e249f37cea1ea6bfb6bdda9f29a8d5))
66
+ * **email:** cover mx lookup non-error rejection ([e52a2b6](https://github.com/visulima/visulima/commit/e52a2b663fe795a1c6f8727dc6ea655425c140b0))
67
+ * **email:** cover nodemailer branch paths and fix lint ([6b5b72b](https://github.com/visulima/visulima/commit/6b5b72b0d8404a25d386d9d6cd0b79199550a1ed))
68
+ * **email:** cover opentelemetry branches and fix lint ([6ac926b](https://github.com/visulima/visulima/commit/6ac926b484a229a66ac38c98dbe8e0c47e431ee8))
69
+ * **email:** cover parse-address domain-literal and quoted-local edge cases ([fa909c7](https://github.com/visulima/visulima/commit/fa909c735d849997f99254f5dcfd0a3bf62b4804))
70
+ * **email:** cover parse-address quote after at-sign ([49143f6](https://github.com/visulima/visulima/commit/49143f65e65b317d1da6e42cab7d2c03f1e63fca))
71
+ * **email:** cover payload builder recipient/template branches ([e629d81](https://github.com/visulima/visulima/commit/e629d814c02576c4b24035235b4fdadf4021a61e))
72
+ * **email:** cover plunk getEmail throw and attachment branches ([c24c2bf](https://github.com/visulima/visulima/commit/c24c2bf0e14247a8bd8538b679ba0f869276107a))
73
+ * **email:** cover plunk send/getEmail branch paths ([96c2097](https://github.com/visulima/visulima/commit/96c2097ae3cb3bec00a57a04d768c83cd816c1db))
74
+ * **email:** cover postal request, attachment and availability branches ([8617ce2](https://github.com/visulima/visulima/commit/8617ce2e43a5244a94434a0995367f5bbb4669b3))
75
+ * **email:** cover postal send and getEmail branches ([9e78f76](https://github.com/visulima/visulima/commit/9e78f769faf6dcb38311995d1f26dee11f2727c8))
76
+ * **email:** cover postmark getEmail, template alias, metadata and attachments ([58236b4](https://github.com/visulima/visulima/commit/58236b47e751a893cca3762e11600fd29fecf4a8))
77
+ * **email:** cover postmark send and template branches ([5c298da](https://github.com/visulima/visulima/commit/5c298da4a20d1125d82cb36cb3c324948ab7c228))
78
+ * **email:** cover provider address formatters ([dd4da80](https://github.com/visulima/visulima/commit/dd4da80afd21074fb145b65d75ad44da5bfaac2f))
79
+ * **email:** cover reply-to validation and mx cache-on-error ([16cc9c2](https://github.com/visulima/visulima/commit/16cc9c2f96368d364271a11a72ec679af5d3df0a))
80
+ * **email:** cover resend getEmail, isAvailable, and full send payload ([e0c4981](https://github.com/visulima/visulima/commit/e0c4981ece5a148c941b3239d53e87dd1b38cb48))
81
+ * **email:** cover resend send, getEmail, availability branches ([bfca30d](https://github.com/visulima/visulima/commit/bfca30d7387d9c351f4b201f21e287fc8a7b2eb6))
82
+ * **email:** cover roundrobin init, wait and failover branches ([8b42738](https://github.com/visulima/visulima/commit/8b427389ff13d9b5cf7d79e318522d54be103ac7))
83
+ * **email:** cover roundrobin name fallbacks and retry paths ([57d56c8](https://github.com/visulima/visulima/commit/57d56c8f8fca3ec17088b09000deac6d0c0707c2))
84
+ * **email:** cover scaleway branch and init failure paths ([5ca4c8f](https://github.com/visulima/visulima/commit/5ca4c8fa479b94d675bc5b6982d6dbe8564a4f0c))
85
+ * **email:** cover scaleway getEmail failure and attachment branches ([1fdbb2f](https://github.com/visulima/visulima/commit/1fdbb2f9404e988129ef485c3ec86a10f870b431))
86
+ * **email:** cover sendgrid getEmail, settings, send failure and attachments ([db99fc8](https://github.com/visulima/visulima/commit/db99fc82e925c5c1187c00185d11872ffc5b5247))
87
+ * **email:** cover sendgrid send and availability branches ([5bca44a](https://github.com/visulima/visulima/commit/5bca44a7fdde5cca30d4079b6ab222271eefb8ad))
88
+ * **email:** cover smime aes128/aes192 algorithm aliases ([715482a](https://github.com/visulima/visulima/commit/715482aeabd6b90949e4a0f70cc9c6b11f0e9170))
89
+ * **email:** cover smime build-message and algorithm paths ([49319c1](https://github.com/visulima/visulima/commit/49319c1c540dc5dd2a1b568791e40e246358c91b))
90
+ * **email:** cover smime empty-body and empty recipient ([bc8ab8c](https://github.com/visulima/visulima/commit/bc8ab8cae1f15c466dfb08eef07312081e358484))
91
+ * **email:** cover smtp 551/553 recipient rejections ([7c4630c](https://github.com/visulima/visulima/commit/7c4630c87496d62c371443412ecb22aabab44be7))
92
+ * **email:** cover smtp branch paths ([d0c135e](https://github.com/visulima/visulima/commit/d0c135eaf7739d1f3025060eaf0bf953c7d41d13))
93
+ * **email:** cover smtp provider via mock-socket harness ([08d98f9](https://github.com/visulima/visulima/commit/08d98f95bb677df4212928a9019cfd7cdf9de898))
94
+ * **email:** cover smtp verify cache write error ([eceaf97](https://github.com/visulima/visulima/commit/eceaf9785081ec4418db0e279009a0c2238a87e8))
95
+ * **email:** cover sweego branch and init failure paths ([99abf7a](https://github.com/visulima/visulima/commit/99abf7a3cd42fba2f4808539f4177368f7186de8))
96
+ * **email:** cover sweego getEmail throw and attachment branches ([139d0ca](https://github.com/visulima/visulima/commit/139d0ca18be97d91a92dbbf9dd6da81693375e0c))
97
+ * **email:** cover to-base64 buffer-less fallback and is-port-available ([c8c7454](https://github.com/visulima/visulima/commit/c8c7454bf0bcf9d734384bac21f24739eeb8de84))
98
+ * **email:** cover validate-email hyphen domain rejection paths ([125eab0](https://github.com/visulima/visulima/commit/125eab0d9a543196496aea6de7d8adf3b3441aae))
99
+ * **email:** cover verify-email mx and smtp branches ([70905d1](https://github.com/visulima/visulima/commit/70905d1430e00efea031116ac675ef30919cee51))
100
+ * **email:** cover verify-smtp socket state machine and caching ([2b84cc1](https://github.com/visulima/visulima/commit/2b84cc1e36c01ef067ec571acdcfe614d9b2d1dc))
101
+ * **email:** cover vue-email default data branch ([c51b812](https://github.com/visulima/visulima/commit/c51b812079f98eca77615c0c08fd206dfb67ad39))
102
+ * **email:** cover zeptomail send error detail and exception branches ([ea74549](https://github.com/visulima/visulima/commit/ea745494f693360cc48d12bfb2bd8af66be0395e))
103
+ * **email:** cover zeptomail send error/header/attachment branches ([5b78e15](https://github.com/visulima/visulima/commit/5b78e15a97a8f0070fe172e5dc92b0a3926bb01b))
104
+ * **email:** fix lint in smtp harness test ([87811af](https://github.com/visulima/visulima/commit/87811af4dc6dea41a5a6ea2f96e3af19b168a0b3))
105
+ * improve coverage across packages ([91bd6d3](https://github.com/visulima/visulima/commit/91bd6d3b61736e3c8bd1fc59b0b5955f76a5d323))
106
+
107
+
108
+ ### Dependencies
109
+
110
+ * **@visulima/error:** upgraded to 6.0.0-alpha.31
111
+ * **@visulima/fs:** upgraded to 5.0.0-alpha.29
112
+
113
+ ## @visulima/email [1.0.0-alpha.33](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.32...@visulima/email@1.0.0-alpha.33) (2026-05-27)
114
+
115
+ ### Bug Fixes
116
+
117
+ * **storage-client:** percent-encode user fields in defaultFingerprint ([7c78a0f](https://github.com/visulima/visulima/commit/7c78a0f9512e2a673b941d80839e9f1e86b7b5d0))
118
+
119
+ ### Documentation
120
+
121
+ * prettier-format agent instructions ([71b6414](https://github.com/visulima/visulima/commit/71b6414528780ac82c4e0bb25b5f4f11faba5549))
122
+
123
+ ### Miscellaneous Chores
124
+
125
+ * sorted package.json ([b47c545](https://github.com/visulima/visulima/commit/b47c545591600fdab17d5cd3a3fbc68b61e199da))
126
+
127
+
128
+ ### Dependencies
129
+
130
+ * **@visulima/disposable-email-domains:** upgraded to 1.0.0-alpha.15
131
+ * **@visulima/error:** upgraded to 6.0.0-alpha.30
132
+ * **@visulima/fs:** upgraded to 5.0.0-alpha.28
133
+ * **@visulima/path:** upgraded to 3.0.0-alpha.11
134
+
1
135
  ## @visulima/email [1.0.0-alpha.32](https://github.com/visulima/visulima/compare/@visulima/email@1.0.0-alpha.31...@visulima/email@1.0.0-alpha.32) (2026-05-26)
2
136
 
3
137
  ### Miscellaneous Chores
package/README.md CHANGED
@@ -114,6 +114,46 @@ const message = new MailMessage().to("user@example.com").subject("Hello").html("
114
114
  await mail.send(message);
115
115
  ```
116
116
 
117
+ ### Capability Guard (fail-fast field support)
118
+
119
+ Every provider declares which capabilities it supports through its `features` flags (attachments, tagging, scheduling, HTML, custom headers, reply-to, templates, ...). Before a message is handed to the provider, `Mail` runs a fail-fast check: if the message uses a capability the provider has **explicitly** declared unsupported (`features[x] === false`), the send is rejected locally — no wasted network round-trip and no silent data loss.
120
+
121
+ ```typescript
122
+ import { createMail } from "@visulima/email";
123
+ import { awsSesProvider } from "@visulima/email/providers/aws-ses";
124
+
125
+ // AWS SES declares `tagging: false`
126
+ const mail = createMail(awsSesProvider({ accessKeyId, secretAccessKey, region }));
127
+
128
+ const result = await mail.send({
129
+ from: { email: "noreply@example.com" },
130
+ subject: "Hi",
131
+ tags: ["promo"], // not representable by AWS SES
132
+ text: "Body",
133
+ to: { email: "user@example.com" },
134
+ });
135
+
136
+ result.success; // false
137
+ (result.error as { code?: string }).code; // "UNSUPPORTED_FEATURES"
138
+ ```
139
+
140
+ Capabilities left `undefined` are treated as "unknown" and never rejected, so providers that publish a partial (or no) `features` map are never falsely blocked. The behaviour is configurable per `Mail` instance:
141
+
142
+ ```typescript
143
+ createMail(provider, { featureCheck: "error" }); // default — reject unsupported fields
144
+ createMail(provider, { featureCheck: "warn" }); // log a warning and send anyway
145
+ createMail(provider, { featureCheck: "off" }); // skip the check entirely
146
+ ```
147
+
148
+ You can also run the check standalone (e.g. inside a custom provider):
149
+
150
+ ```typescript
151
+ import { checkFeatureSupport } from "@visulima/email";
152
+ // or the focused entry point: "@visulima/email/validation/check-feature-support"
153
+
154
+ const { supported, violations } = checkFeatureSupport(message, provider.features);
155
+ ```
156
+
117
157
  ### Creating Draft Emails
118
158
 
119
159
  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:
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { E as EmailSigner, a as EmailEncrypter } from "./packem_shared/types.d-C
6
6
  import { T as TemplateRenderer } from "./packem_shared/types.d-nyR1ty1Q.js";
7
7
  import { a as Provider } from "./packem_shared/provider.d-DcfYeGJJ.js";
8
8
  export { type P as ProviderFactory, d as defineProvider } from "./packem_shared/provider.d-DcfYeGJJ.js";
9
+ export { type FeatureSupportResult, type FeatureViolation, default as checkFeatureSupport } from "./utils/validation/check-feature-support.js";
9
10
  /**
10
11
  * Detects MIME type from filename using mime package.
11
12
  * Falls back to application/octet-stream if not found.
@@ -585,6 +586,23 @@ declare class DraftMailMessage extends MailMessage {
585
586
  */
586
587
  type SendableMessage = MailMessage | EmailOptions;
587
588
  /**
589
+ * Controls the fail-fast capability guard that runs before a message is handed to the provider.
590
+ *
591
+ * `"error"` (default) rejects the send with a failed Result when the message uses a capability the provider has explicitly declared unsupported.
592
+ * `"warn"` logs a warning and sends anyway. `"off"` skips the check entirely.
593
+ */
594
+ type FeatureCheckMode = "error" | "off" | "warn";
595
+ /**
596
+ * Options for a {@link Mail} instance.
597
+ */
598
+ interface MailOptions {
599
+ /**
600
+ * How to handle messages that use capabilities the provider has declared unsupported.
601
+ * @default "error"
602
+ */
603
+ featureCheck?: FeatureCheckMode;
604
+ }
605
+ /**
588
606
  * Global email configuration that applies to all emails sent through a Mail instance.
589
607
  */
590
608
  interface MailGlobalConfig {
@@ -617,11 +635,13 @@ declare class Mail {
617
635
  private logger?;
618
636
  private loggerInstance?;
619
637
  private globalConfig?;
638
+ private readonly featureCheck;
620
639
  /**
621
640
  * Creates a new Mail instance with a provider.
622
641
  * @param provider The email provider instance.
642
+ * @param options Optional Mail configuration.
623
643
  */
624
- constructor(provider: Provider);
644
+ constructor(provider: Provider, options?: MailOptions);
625
645
  /**
626
646
  * Sets the logger instance for this mail instance.
627
647
  * @param logger The logger instance (Console) to use for logging.
@@ -742,6 +762,13 @@ declare class Mail {
742
762
  signal?: AbortSignal;
743
763
  }): AsyncIterable<Receipt>;
744
764
  /**
765
+ * Runs the fail-fast capability guard for the configured {@link FeatureCheckMode}.
766
+ * @param emailOptions The fully-resolved email options about to be sent.
767
+ * @returns An {@link EmailError} when the send should be rejected, otherwise undefined.
768
+ * @private
769
+ */
770
+ private assertFeatureSupport;
771
+ /**
745
772
  * Builds email options from a MailMessage instance for draft creation.
746
773
  * Applies global configuration before building to ensure required fields are set.
747
774
  * @param message The MailMessage or DraftMailMessage instance.
@@ -774,12 +801,16 @@ declare class Mail {
774
801
  /**
775
802
  * Creates a new Mail instance with a provider.
776
803
  * @param provider The email provider instance.
804
+ * @param options Optional Mail configuration, e.g. the {@link FeatureCheckMode}.
777
805
  * @returns A new Mail instance.
778
806
  * @example
779
807
  * ```ts
780
808
  * const mail = createMail(provider);
781
809
  * mail.setFrom({ email: "noreply@example.com", name: "My App" });
810
+ *
811
+ * // Disable the fail-fast capability guard
812
+ * const lenient = createMail(provider, { featureCheck: "off" });
782
813
  * ```
783
814
  */
784
- declare const createMail: (provider: Provider) => Mail;
785
- export { type Attachment, type AttachmentDataOptions, type AttachmentOptions, type CalendarEventOptions, type EmailAddress, EmailError, type EmailHeaders, type EmailOptions, type EmailResult, Mail, type MailGlobalConfig, MailMessage, type Priority, type Provider, type Receipt, RequiredOptionError, type Result, type SendableMessage, type TemplateRenderer, createMail, detectMimeType, generateContentId, readFileAsBuffer };
815
+ declare const createMail: (provider: Provider, options?: MailOptions) => Mail;
816
+ export { type Attachment, type AttachmentDataOptions, type AttachmentOptions, type CalendarEventOptions, type EmailAddress, EmailError, type EmailHeaders, type EmailOptions, type EmailResult, type FeatureCheckMode, Mail, type MailGlobalConfig, MailMessage, type MailOptions, type Priority, type Provider, type Receipt, RequiredOptionError, type Result, type SendableMessage, type TemplateRenderer, createMail, detectMimeType, generateContentId, readFileAsBuffer };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{detectMimeType as o,generateContentId as t,readFileAsBuffer as a}from"./packem_shared/detectMimeType-bo5oxfnt.js";import{default as i}from"./packem_shared/EmailError-zm2ffVav.js";import{default as m}from"./packem_shared/RequiredOptionError-CevW3u2K.js";import{Mail as l,createMail as x}from"./packem_shared/Mail-D93oo_9g.js";import{MailMessage as s}from"./packem_shared/MailMessage-BvaH5tZZ.js";import{defineProvider as u}from"./packem_shared/defineProvider-CtLH0VBE.js";export{i as EmailError,l as Mail,s as MailMessage,m as RequiredOptionError,x as createMail,u as defineProvider,o as detectMimeType,t as generateContentId,a as readFileAsBuffer};
1
+ import{detectMimeType as o,generateContentId as t,readFileAsBuffer as a}from"./packem_shared/detectMimeType-bo5oxfnt.js";import{default as p}from"./packem_shared/EmailError-zm2ffVav.js";import{default as d}from"./packem_shared/RequiredOptionError-CevW3u2K.js";import{Mail as l,createMail as u}from"./packem_shared/Mail-BWSWWq6m.js";import{MailMessage as s}from"./packem_shared/MailMessage-BvaH5tZZ.js";import{defineProvider as M}from"./packem_shared/defineProvider-CtLH0VBE.js";import{default as E}from"./utils/validation/check-feature-support.js";export{p as EmailError,l as Mail,s as MailMessage,d as RequiredOptionError,E as checkFeatureSupport,u as createMail,M as defineProvider,o as detectMimeType,t as generateContentId,a as readFileAsBuffer};
@@ -0,0 +1 @@
1
+ var p=Object.defineProperty;var n=(a,e)=>p(a,"name",{value:e,configurable:!0});import{MailMessage as c}from"./MailMessage-BvaH5tZZ.js";import m from"./EmailError-zm2ffVav.js";import{z as b}from"./build-mime-message-6xstxaUV.js";import{c as y}from"./create-logger-BiWdqFNg.js";import{i as d}from"./headers-to-record-Qo124ImV.js";import C from"../utils/validation/check-feature-support.js";var v=Object.defineProperty,M=n((a,e)=>v(a,"name",{value:e,configurable:!0}),"s");class f extends c{static{n(this,"DraftMailMessage")}static{M(this,"DraftMailMessage")}constructor(){super(),this.header("X-Unsent","1")}}var w=Object.defineProperty,u=n((a,e)=>w(a,"name",{value:e,configurable:!0}),"d");const T=u((a,e,r)=>{e.success&&e.data?a&&a.info("Email sent successfully",{messageId:e.data.messageId,provider:e.data.provider}):a&&a.error("Email send failed",{error:e.error,provider:r})},"logSendResult");class h{static{n(this,"Mail")}static{u(this,"Mail")}static extractErrorMessages(e){return e instanceof Error?[e.message]:[String(e??"Unknown error")]}provider;logger;loggerInstance;globalConfig;featureCheck;constructor(e,r){this.provider=e,this.featureCheck=r?.featureCheck??"error"}setLogger(e){return this.loggerInstance=e,this.logger=y("Mail",e),this}setFrom(e){return this.globalConfig??={},this.globalConfig.from=e,this.logger&&this.logger.debug("Default from address updated",{from:e.email}),this}setReplyTo(e){return this.globalConfig??={},this.globalConfig.replyTo=e,this.logger&&this.logger.debug("Default reply-to address updated",{replyTo:e.email}),this}setHeaders(e){if(this.globalConfig??={},this.globalConfig.headers=e,this.logger){const r=d(e);this.logger.debug("Default headers updated",{count:Object.keys(r).length,headers:Object.keys(r)})}return this}async draft(e){let r=e instanceof c||e instanceof f?await this.buildDraftFromMessage(e):await this.buildDraftFromOptions(e);r=this.applyGlobalConfig(r);const t=r.headers?d(r.headers):{};r.headers={...t,"X-Unsent":"1"};const s=await b(r);return this.logger&&this.logger.debug("Draft created successfully in EML format",{from:r.from.email,size:s.length,subject:r.subject,to:Array.isArray(r.to)?r.to.length:1}),s}async send(e){if(e instanceof f)throw new TypeError("Cannot send draft messages. Convert to MailMessage first or remove X-Unsent header.");let r;if(e instanceof c)this.logger&&this.logger.debug("Sending MailMessage instance"),this.loggerInstance&&e.setLogger(this.loggerInstance),r=await e.build();else{const o=e;this.logger&&this.logger.debug("Sending email with options",{subject:o.subject,to:Array.isArray(o.to)?o.to.length:1}),r=o}r=this.applyGlobalConfig(r);const t=this.assertFeatureSupport(r);if(t)return{error:t,success:!1};const s=await this.provider.sendEmail(r);return T(this.logger,s,this.provider.name??"unknown"),s}async*sendMany(e,r){const t=this.provider.name;let s=0,o=0,g=0;this.logger&&this.logger.debug("Starting batch email send",{provider:t});for await(const l of e){if(l instanceof f)throw new TypeError("Cannot send draft messages. Convert to MailMessage first or remove X-Unsent header.");if(r?.signal?.aborted){this.logger&&this.logger.warn("Batch send operation was aborted",{failed:g,processed:s,successful:o}),yield{errorMessages:["Send operation was aborted"],provider:t,successful:!1};return}s+=1;try{const i=await this.send(l);i.success&&i.data?(o+=1,this.logger&&this.logger.debug(`Email ${s} sent successfully`,{messageId:i.data.messageId}),yield{messageId:i.data.messageId,provider:i.data.provider??t,response:i.data.response,successful:!0,timestamp:i.data.timestamp}):(g+=1,this.logger&&this.logger.error(`Email ${s} send failed`,{error:i.error}),yield{errorMessages:h.extractErrorMessages(i.error),provider:t,successful:!1})}catch(i){g+=1,this.logger&&this.logger.error(`Email ${s} send failed with exception`,i),yield{errorMessages:h.extractErrorMessages(i),provider:t,successful:!1}}}this.logger&&this.logger.debug("Batch email send completed",{failureCount:g,processedCount:s,provider:t,successCount:o})}assertFeatureSupport(e){if(this.featureCheck==="off")return;const{supported:r,violations:t}=C(e,this.provider.features);if(r)return;const s=this.provider.name??"unknown",o=t.map(l=>l.field),g=t.map(l=>l.message);if(this.featureCheck==="warn"){this.logger&&this.logger.warn("Message uses capabilities the provider does not support",{fields:o,provider:s});return}return this.logger&&this.logger.error("Message rejected by capability guard",{fields:o,provider:s}),new m(s,`Provider does not support the following message fields: ${o.join(", ")}`,{code:"UNSUPPORTED_FEATURES",hint:g})}async buildDraftFromMessage(e){return this.logger&&this.logger.debug("Creating draft from MailMessage instance"),this.loggerInstance&&e.setLogger(this.loggerInstance),this.applyGlobalConfigToMessage(e),await e.build()}async buildDraftFromOptions(e){return this.logger&&this.logger.debug("Creating draft from email options",{subject:e.subject,to:Array.isArray(e.to)?e.to.length:1}),e}applyGlobalConfigToMessage(e){const r=this.globalConfig?.from;r&&!e.getFrom()&&e.from(r);const t=this.globalConfig?.replyTo;if(t&&!e.getReplyTo()&&e.replyTo(t),this.globalConfig?.headers){const s=d(this.globalConfig.headers);Object.entries(s).forEach(([o,g])=>{e.header(o,g)})}}applyGlobalConfig(e){if(!this.globalConfig)return e;const r={...e};if(!r.from&&this.globalConfig.from&&(r.from=this.globalConfig.from,this.logger&&this.logger.debug("Applied global from address",{from:this.globalConfig.from.email})),!r.replyTo&&this.globalConfig.replyTo&&(r.replyTo=this.globalConfig.replyTo,this.logger&&this.logger.debug("Applied global reply-to address",{replyTo:this.globalConfig.replyTo.email})),this.globalConfig.headers){const t=d(this.globalConfig.headers),s=r.headers?d(r.headers):{};r.headers={...t,...s},this.logger&&this.logger.debug("Merged global headers",{globalCount:Object.keys(t).length,messageCount:Object.keys(s).length})}return r}}const A=u((a,e)=>new h(a,e),"createMail");export{h as Mail,A as createMail};
@@ -0,0 +1 @@
1
+ var u=Object.defineProperty;var o=(e,r)=>u(e,"name",{value:r,configurable:!0});var c=Object.defineProperty,i=o((e,r)=>c(e,"name",{value:r,configurable:!0}),"n");const l=["attachments","batchSending","customHeaders","html","replyTo","scheduling","tagging","templates","tracking"],f=i(e=>typeof e=="function","isProviderFactory"),g=i(e=>e!==null&&typeof e=="object"&&"initialize"in e&&"isAvailable"in e&&"sendEmail"in e,"isProvider"),v=i(e=>{if(g(e))return e.features;if(f(e))try{return e({}).features}catch{return}},"resolveFeatures");function d(e){const r=e.map(t=>v(t)).filter(t=>t!==void 0),a={};if(r.length===0)return a;for(const t of l){const s=r.map(n=>n[t]);s.every(n=>n===!0)?a[t]=!0:s.every(n=>n===!1)&&(a[t]=!1)}return a}o(d,"l");i(d,"aggregateProviderFeatures");export{d as l};
@@ -0,0 +1 @@
1
+ var w=Object.defineProperty;var h=(e,s)=>w(e,"name",{value:s,configurable:!0});import u from"./EmailError-zm2ffVav.js";import $ from"./RequiredOptionError-CevW3u2K.js";import{c as E}from"./create-logger-BiWdqFNg.js";import{s as A}from"./generate-message-id-D4uA8gkj.js";import{defineProvider as z}from"./defineProvider-CtLH0VBE.js";import{l as P}from"./aggregate-features-BvgdYul-.js";var F=Object.defineProperty,p=h((e,s)=>F(e,"name",{value:s,configurable:!0}),"g");const k=p(e=>typeof e=="function","isProviderFactory"),S=p(e=>e!==null&&typeof e=="object"&&"sendEmail"in e&&"initialize"in e&&"isAvailable"in e,"isProvider"),l="failover",T=z((e={})=>{if(!e.mailers||e.mailers.length===0)throw new $(l,"mailers");const s={debug:e.debug??!1,mailers:e.mailers,retryAfter:e.retryAfter??60};let b=!1;const t=[],n=E(l,e.logger);let g;const m=p(async()=>g||(g=(async()=>{t.length=0;for(const a of s.mailers)try{let r;if(k(a))r=a({});else if(S(a))r=a;else{n.debug(`Skipping invalid mailer: ${a}`);continue}try{await r.initialize(),t.push(r),n.debug(`Initialized provider: ${r.name??"unknown"}`)}catch(c){n.debug(`Failed to initialize provider ${r.name??"unknown"}:`,c)}}catch(r){n.debug("Error processing mailer:",r)}if(t.length===0)throw new u(l,"No providers could be initialized")})(),g),"initializeProviders");return{features:P(s.mailers),async initialize(){if(!b)try{await m(),b=!0,n.debug(`Failover provider initialized with ${t.length} provider(s)`)}catch(a){throw new u(l,`Failed to initialize: ${a.message}`,{cause:a})}},async isAvailable(){try{t.length===0&&await m();for(const a of t)try{if(await a.isAvailable())return!0}catch{}return!1}catch{return!1}},name:l,options:s,async sendEmail(a){try{if(t.length===0&&await m(),t.length===0)return{error:new u(l,"No providers available"),success:!1};const r=[];let c;for(let o=0;o<t.length;o+=1){const f=t[o];if(!f)continue;const d=f.name??`provider-${o+1}`;n.debug(`Attempting to send email via ${d} (${o+1}/${t.length})`);try{if(!await f.isAvailable()){n.debug(`Provider ${d} is not available, skipping`),r.push(new u(l,`Provider ${d} is not available`));continue}const i=await f.sendEmail(a);if(i.success&&i.data)return n.debug(`Email sent successfully via ${d}`),{data:{...i.data,messageId:i.data.messageId||A(),provider:`${l}(${d})`},success:!0};if(i.error){c=i.error,r.push(i.error);const v=i.error instanceof Error?i.error.message:String(i.error);n.debug(`Failed to send via ${d}:`,v)}}catch(i){c=i instanceof Error?i:new Error(String(i)),r.push(c);const v=c instanceof Error?c.message:String(c);n.debug(`Exception sending via ${d}:`,v)}o<t.length-1&&s.retryAfter>0&&(n.debug(`Waiting ${s.retryAfter}ms before trying next provider`),await new Promise(i=>{setTimeout(()=>{i()},s.retryAfter)}))}const y=r.map(o=>o instanceof Error?o.message:String(o)).join("; ");return{error:new u(l,`All providers failed. Errors: ${y}`,{cause:c}),success:!1}}catch(r){return{error:new u(l,`Failed to send email: ${r.message}`,{cause:r}),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{T as default};
@@ -0,0 +1 @@
1
+ var E=Object.defineProperty;var y=(e,l)=>E(e,"name",{value:l,configurable:!0});import f from"./EmailError-zm2ffVav.js";import z from"./RequiredOptionError-CevW3u2K.js";import{c as A}from"./create-logger-BiWdqFNg.js";import{defineProvider as P}from"./defineProvider-CtLH0VBE.js";import{l as k}from"./aggregate-features-BvgdYul-.js";var x=Object.defineProperty,m=y((e,l)=>x(e,"name",{value:l,configurable:!0}),"f");const S=m(e=>typeof e=="function","isProviderFactory"),F=m(e=>e!=null&&typeof e=="object"&&"sendEmail"in e&&"initialize"in e&&"isAvailable"in e,"isProvider"),o="roundrobin",L=P((e={})=>{if(!e.mailers||e.mailers.length===0)throw new z(o,"mailers");const l={debug:e.debug??!1,logger:e.logger,mailers:e.mailers,retries:e.retries??3,retryAfter:e.retryAfter??60,timeout:e.timeout??3e4};let p=!1;const i=[];let a=0;const n=A(o,e.logger),v=m(async()=>{i.length=0;for(const t of l.mailers)try{let r;if(S(t))r=t({});else if(F(t))r=t;else{n.debug(`Skipping invalid mailer: ${t}`);continue}try{await r.initialize(),i.push(r),n.debug(`Initialized provider: ${r.name??"unknown"}`)}catch(s){n.debug(`Failed to initialize provider ${r.name??"unknown"}:`,s)}}catch(r){n.debug("Error processing mailer:",r)}if(i.length===0)throw new f(o,"No providers could be initialized");a=Math.floor(Math.random()*i.length),n.debug(`Round robin starting at index ${a} (${i[a]?.name??"unknown"})`)},"initializeProviders"),b=m(async()=>{if(i.length===0)return;const t=a;let r=0;for(;r<i.length;){const s=i[a];if(!s){a=(a+1)%i.length,r+=1;continue}const g=s.name??`provider-${a+1}`;try{if(await s.isAvailable())return a=(a+1)%i.length,n.debug(`Selected provider: ${g} (index ${a===0?i.length-1:a-1})`),s}catch{}a=(a+1)%i.length,r+=1,r<i.length&&l.retryAfter>0&&await new Promise(u=>{setTimeout(()=>{u(void 0)},l.retryAfter)})}return n.debug(`No available providers found, using provider at index ${t}`),i[t]??void 0},"getNextProvider");return{features:k(l.mailers),async initialize(){if(!p)try{await v(),p=!0,n.debug(`Round robin provider initialized with ${i.length} provider(s)`)}catch(t){throw new f(o,`Failed to initialize: ${t.message}`,{cause:t})}},async isAvailable(){try{return i.length===0&&await v(),(await Promise.allSettled(i.map(t=>t.isAvailable()))).some(t=>t.status==="fulfilled"&&t.value)}catch{return!1}},name:o,options:l,async sendEmail(t){try{if(i.length===0&&await v(),i.length===0)return{error:new f(o,"No providers available"),success:!1};const r=await b();if(!r)return{error:new f(o,"No available providers found"),success:!1};const s=r.name??"unknown";n.debug(`Sending email via ${s}`);const g=await r.sendEmail(t);if(g.success)return n.debug(`Email sent successfully via ${s}`),{data:{...g.data,provider:`${o}(${s})`},success:!0};n.debug(`Failed to send via ${s}, trying next provider`);const u=[];g.error&&u.push(g.error);let w=0;for(;w<i.length-1;){const d=await b();if(!d||d===r)break;const h=d.name??"unknown";n.debug(`Retrying with ${h}`);try{const c=await d.sendEmail(t);if(c.success)return n.debug(`Email sent successfully via ${h} (after retry)`),{data:{...c.data,provider:`${o}(${h})`},success:!0};c.error&&u.push(c.error)}catch(c){u.push(c instanceof Error?c:new Error(String(c)))}w+=1}const $=u.map(d=>d instanceof Error?d.message:String(d)).join("; ");return{error:new f(o,`Failed to send email via all providers. Errors: ${$}`,{cause:u[0]instanceof Error?u[0]:new Error(String(u[0]))}),success:!1}}catch(r){return{error:new f(o,`Failed to send email: ${r.message}`,{cause:r}),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{L as default};
@@ -1 +1 @@
1
- import{default as o}from"../../packem_shared/failoverProvider-W5ctAhdC.js";export{o as failoverProvider};
1
+ import{default as o}from"../../packem_shared/failoverProvider-D90MHT2T.js";export{o as failoverProvider};
@@ -1 +1 @@
1
- import{default as d}from"../../packem_shared/roundRobinProvider-FvZQKl6n.js";export{d as roundRobinProvider};
1
+ import{default as d}from"../../packem_shared/roundRobinProvider-B_60F0BC.js";export{d as roundRobinProvider};
@@ -0,0 +1,53 @@
1
+ import { F as FeatureFlags, E as EmailOptions } from "../../packem_shared/types.d-wwpgA3to.js";
2
+ import 'node:buffer';
3
+ /**
4
+ * A single capability that a message uses but the target provider cannot represent.
5
+ */
6
+ interface FeatureViolation {
7
+ /**
8
+ * The provider capability flag that is not supported.
9
+ */
10
+ feature: keyof FeatureFlags;
11
+ /**
12
+ * The message field that triggered the check.
13
+ */
14
+ field: string;
15
+ /**
16
+ * Human-readable explanation of the violation.
17
+ */
18
+ message: string;
19
+ }
20
+ /**
21
+ * Result of a fail-fast provider capability check.
22
+ */
23
+ interface FeatureSupportResult {
24
+ /**
25
+ * True when the message uses no capability the provider has explicitly disabled.
26
+ */
27
+ supported: boolean;
28
+ /**
29
+ * The list of capabilities the message uses that the provider cannot represent.
30
+ */
31
+ violations: FeatureViolation[];
32
+ }
33
+ /**
34
+ * Performs a fail-fast field-support check for a message against a provider's declared capabilities.
35
+ *
36
+ * A capability counts as a violation only when the message actually uses it **and** the provider
37
+ * has explicitly declared it unsupported (`features[capability] === false`). Capabilities left
38
+ * `undefined` are treated as "unknown" and never raise a violation, so providers that publish a
39
+ * partial (or no) {@link FeatureFlags} map are never falsely rejected.
40
+ * @param options The normalized email options about to be sent.
41
+ * @param features The target provider's declared capabilities. When omitted the check is a no-op.
42
+ * @returns A {@link FeatureSupportResult} describing any unsupported capabilities in use.
43
+ * @example
44
+ * ```ts
45
+ * const { supported, violations } = checkFeatureSupport(message, provider.features);
46
+ *
47
+ * if (!supported) {
48
+ * throw new Error(violations.map((v) => v.message).join(", "));
49
+ * }
50
+ * ```
51
+ */
52
+ declare function checkFeatureSupport(options: EmailOptions, features?: FeatureFlags): FeatureSupportResult;
53
+ export { FeatureSupportResult, FeatureViolation, checkFeatureSupport as default };
@@ -0,0 +1 @@
1
+ var i=Object.defineProperty;var l=(e,a)=>i(e,"name",{value:a,configurable:!0});import{i as u}from"../../packem_shared/headers-to-record-Qo124ImV.js";var n=Object.defineProperty,t=l((e,a)=>n(e,"name",{value:a,configurable:!0}),"t");const o=t(e=>Array.isArray(e)&&e.length>0,"hasItems"),c=t((e,a)=>a.find(r=>e[r]!==void 0),"firstSetField"),f=[{detect:t(e=>o(e.attachments)?"attachments":void 0,"detect"),feature:"attachments",label:"attachments"},{detect:t(e=>typeof e.html=="string"&&e.html.length>0?"html":void 0,"detect"),feature:"html",label:"HTML content"},{detect:t(e=>e.replyTo?"replyTo":void 0,"detect"),feature:"replyTo",label:"a reply-to address"},{detect:t(e=>e.headers!==void 0&&Object.keys(u(e.headers)).length>0?"headers":void 0,"detect"),feature:"customHeaders",label:"custom headers"},{detect:t(e=>o(e.tags)?"tags":void 0,"detect"),feature:"tagging",label:"tags"},{detect:t(e=>c(e,["scheduledAt","sendAt"]),"detect"),feature:"scheduling",label:"scheduled delivery"},{detect:t(e=>c(e,["templateId","template"]),"detect"),feature:"templates",label:"provider templates"}];function h(e,a){if(!a)return{supported:!0,violations:[]};const r=[];for(const d of f){if(a[d.feature]!==!1)continue;const s=d.detect(e);s!==void 0&&r.push({feature:d.feature,field:s,message:`Message uses ${d.label} (field "${s}") but the provider does not support the "${d.feature}" capability`})}return{supported:r.length===0,violations:r}}l(h,"f");t(h,"checkFeatureSupport");export{h as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/email",
3
- "version": "1.0.0-alpha.32",
3
+ "version": "1.0.0-alpha.34",
4
4
  "description": "A comprehensive email library with multi-provider support, crypto utilities, and template engines",
5
5
  "keywords": [
6
6
  "ahasend",
@@ -49,9 +49,7 @@
49
49
  "zeptomail"
50
50
  ],
51
51
  "homepage": "https://visulima.com/packages/email",
52
- "bugs": {
53
- "url": "https://github.com/visulima/visulima/issues"
54
- },
52
+ "bugs": "https://github.com/visulima/visulima/issues",
55
53
  "repository": {
56
54
  "type": "git",
57
55
  "url": "git+https://github.com/visulima/visulima.git",
@@ -251,6 +249,10 @@
251
249
  "types": "./dist/utils/validation/verify-email.d.ts",
252
250
  "default": "./dist/utils/validation/verify-email.js"
253
251
  },
252
+ "./validation/check-feature-support": {
253
+ "types": "./dist/utils/validation/check-feature-support.d.ts",
254
+ "default": "./dist/utils/validation/check-feature-support.js"
255
+ },
254
256
  "./utils/normalize-email-aliases": {
255
257
  "types": "./dist/utils/normalize-email-aliases.d.ts",
256
258
  "default": "./dist/utils/normalize-email-aliases.js"
@@ -263,7 +265,7 @@
263
265
  "CHANGELOG.md"
264
266
  ],
265
267
  "dependencies": {
266
- "@visulima/disposable-email-domains": "1.0.0-alpha.14",
268
+ "@visulima/disposable-email-domains": "1.0.0-alpha.15",
267
269
  "html-to-text": "^10.0.0",
268
270
  "ical-generator": "10.2.0",
269
271
  "lru-cache": "11.4.0",
@@ -1 +0,0 @@
1
- var p=Object.defineProperty;var l=(o,e)=>p(o,"name",{value:e,configurable:!0});import{MailMessage as f}from"./MailMessage-BvaH5tZZ.js";import{z as b}from"./build-mime-message-6xstxaUV.js";import{c as m}from"./create-logger-BiWdqFNg.js";import{i as n}from"./headers-to-record-Qo124ImV.js";var y=Object.defineProperty,C=l((o,e)=>y(o,"name",{value:e,configurable:!0}),"s");class h extends f{static{l(this,"DraftMailMessage")}static{C(this,"DraftMailMessage")}constructor(){super(),this.header("X-Unsent","1")}}var M=Object.defineProperty,c=l((o,e)=>M(o,"name",{value:e,configurable:!0}),"n");const v=c((o,e,s)=>{e.success&&e.data?o&&o.info("Email sent successfully",{messageId:e.data.messageId,provider:e.data.provider}):o&&o.error("Email send failed",{error:e.error,provider:s})},"logSendResult");class d{static{l(this,"Mail")}static{c(this,"Mail")}static extractErrorMessages(e){return e instanceof Error?[e.message]:[String(e??"Unknown error")]}provider;logger;loggerInstance;globalConfig;constructor(e){this.provider=e}setLogger(e){return this.loggerInstance=e,this.logger=m("Mail",e),this}setFrom(e){return this.globalConfig??={},this.globalConfig.from=e,this.logger&&this.logger.debug("Default from address updated",{from:e.email}),this}setReplyTo(e){return this.globalConfig??={},this.globalConfig.replyTo=e,this.logger&&this.logger.debug("Default reply-to address updated",{replyTo:e.email}),this}setHeaders(e){if(this.globalConfig??={},this.globalConfig.headers=e,this.logger){const s=n(e);this.logger.debug("Default headers updated",{count:Object.keys(s).length,headers:Object.keys(s)})}return this}async draft(e){let s=e instanceof f||e instanceof h?await this.buildDraftFromMessage(e):await this.buildDraftFromOptions(e);s=this.applyGlobalConfig(s);const t=s.headers?n(s.headers):{};s.headers={...t,"X-Unsent":"1"};const r=await b(s);return this.logger&&this.logger.debug("Draft created successfully in EML format",{from:s.from.email,size:r.length,subject:s.subject,to:Array.isArray(s.to)?s.to.length:1}),r}async send(e){if(e instanceof h)throw new TypeError("Cannot send draft messages. Convert to MailMessage first or remove X-Unsent header.");let s;if(e instanceof f)this.logger&&this.logger.debug("Sending MailMessage instance"),this.loggerInstance&&e.setLogger(this.loggerInstance),s=await e.build();else{const r=e;this.logger&&this.logger.debug("Sending email with options",{subject:r.subject,to:Array.isArray(r.to)?r.to.length:1}),s=r}s=this.applyGlobalConfig(s);const t=await this.provider.sendEmail(s);return v(this.logger,t,this.provider.name??"unknown"),t}async*sendMany(e,s){const t=this.provider.name;let r=0,g=0,i=0;this.logger&&this.logger.debug("Starting batch email send",{provider:t});for await(const u of e){if(u instanceof h)throw new TypeError("Cannot send draft messages. Convert to MailMessage first or remove X-Unsent header.");if(s?.signal?.aborted){this.logger&&this.logger.warn("Batch send operation was aborted",{failed:i,processed:r,successful:g}),yield{errorMessages:["Send operation was aborted"],provider:t,successful:!1};return}r+=1;try{const a=await this.send(u);a.success&&a.data?(g+=1,this.logger&&this.logger.debug(`Email ${r} sent successfully`,{messageId:a.data.messageId}),yield{messageId:a.data.messageId,provider:a.data.provider??t,response:a.data.response,successful:!0,timestamp:a.data.timestamp}):(i+=1,this.logger&&this.logger.error(`Email ${r} send failed`,{error:a.error}),yield{errorMessages:d.extractErrorMessages(a.error),provider:t,successful:!1})}catch(a){i+=1,this.logger&&this.logger.error(`Email ${r} send failed with exception`,a),yield{errorMessages:d.extractErrorMessages(a),provider:t,successful:!1}}}this.logger&&this.logger.debug("Batch email send completed",{failureCount:i,processedCount:r,provider:t,successCount:g})}async buildDraftFromMessage(e){return this.logger&&this.logger.debug("Creating draft from MailMessage instance"),this.loggerInstance&&e.setLogger(this.loggerInstance),this.applyGlobalConfigToMessage(e),await e.build()}async buildDraftFromOptions(e){return this.logger&&this.logger.debug("Creating draft from email options",{subject:e.subject,to:Array.isArray(e.to)?e.to.length:1}),e}applyGlobalConfigToMessage(e){const s=this.globalConfig?.from;s&&!e.getFrom()&&e.from(s);const t=this.globalConfig?.replyTo;if(t&&!e.getReplyTo()&&e.replyTo(t),this.globalConfig?.headers){const r=n(this.globalConfig.headers);Object.entries(r).forEach(([g,i])=>{e.header(g,i)})}}applyGlobalConfig(e){if(!this.globalConfig)return e;const s={...e};if(!s.from&&this.globalConfig.from&&(s.from=this.globalConfig.from,this.logger&&this.logger.debug("Applied global from address",{from:this.globalConfig.from.email})),!s.replyTo&&this.globalConfig.replyTo&&(s.replyTo=this.globalConfig.replyTo,this.logger&&this.logger.debug("Applied global reply-to address",{replyTo:this.globalConfig.replyTo.email})),this.globalConfig.headers){const t=n(this.globalConfig.headers),r=s.headers?n(s.headers):{};s.headers={...t,...r},this.logger&&this.logger.debug("Merged global headers",{globalCount:Object.keys(t).length,messageCount:Object.keys(r).length})}return s}}const D=c(o=>new d(o),"createMail");export{d as Mail,D as createMail};
@@ -1 +0,0 @@
1
- var w=Object.defineProperty;var b=(e,l)=>w(e,"name",{value:l,configurable:!0});import u from"./EmailError-zm2ffVav.js";import $ from"./RequiredOptionError-CevW3u2K.js";import{c as E}from"./create-logger-BiWdqFNg.js";import{s as A}from"./generate-message-id-D4uA8gkj.js";import{defineProvider as z}from"./defineProvider-CtLH0VBE.js";var P=Object.defineProperty,p=b((e,l)=>P(e,"name",{value:l,configurable:!0}),"v");const F=p(e=>typeof e=="function","isProviderFactory"),k=p(e=>e!==null&&typeof e=="object"&&"sendEmail"in e&&"initialize"in e&&"isAvailable"in e,"isProvider"),o="failover",C=z((e={})=>{if(!e.mailers||e.mailers.length===0)throw new $(o,"mailers");const l={debug:e.debug??!1,mailers:e.mailers,retryAfter:e.retryAfter??60};let h=!1;const t=[],n=E(o,e.logger);let g;const m=p(async()=>g||(g=(async()=>{t.length=0;for(const a of l.mailers)try{let r;if(F(a))r=a({});else if(k(a))r=a;else{n.debug(`Skipping invalid mailer: ${a}`);continue}try{await r.initialize(),t.push(r),n.debug(`Initialized provider: ${r.name??"unknown"}`)}catch(c){n.debug(`Failed to initialize provider ${r.name??"unknown"}:`,c)}}catch(r){n.debug("Error processing mailer:",r)}if(t.length===0)throw new u(o,"No providers could be initialized")})(),g),"initializeProviders");return{features:{attachments:!0,batchSending:!1,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!1,templates:!1,tracking:!1},async initialize(){if(!h)try{await m(),h=!0,n.debug(`Failover provider initialized with ${t.length} provider(s)`)}catch(a){throw new u(o,`Failed to initialize: ${a.message}`,{cause:a})}},async isAvailable(){try{t.length===0&&await m();for(const a of t)try{if(await a.isAvailable())return!0}catch{}return!1}catch{return!1}},name:o,options:l,async sendEmail(a){try{if(t.length===0&&await m(),t.length===0)return{error:new u(o,"No providers available"),success:!1};const r=[];let c;for(let s=0;s<t.length;s+=1){const f=t[s];if(!f)continue;const d=f.name??`provider-${s+1}`;n.debug(`Attempting to send email via ${d} (${s+1}/${t.length})`);try{if(!await f.isAvailable()){n.debug(`Provider ${d} is not available, skipping`),r.push(new u(o,`Provider ${d} is not available`));continue}const i=await f.sendEmail(a);if(i.success&&i.data)return n.debug(`Email sent successfully via ${d}`),{data:{...i.data,messageId:i.data.messageId||A(),provider:`${o}(${d})`},success:!0};if(i.error){c=i.error,r.push(i.error);const v=i.error instanceof Error?i.error.message:String(i.error);n.debug(`Failed to send via ${d}:`,v)}}catch(i){c=i instanceof Error?i:new Error(String(i)),r.push(c);const v=c instanceof Error?c.message:String(c);n.debug(`Exception sending via ${d}:`,v)}s<t.length-1&&l.retryAfter>0&&(n.debug(`Waiting ${l.retryAfter}ms before trying next provider`),await new Promise(i=>{setTimeout(()=>{i()},l.retryAfter)}))}const y=r.map(s=>s instanceof Error?s.message:String(s)).join("; ");return{error:new u(o,`All providers failed. Errors: ${y}`,{cause:c}),success:!1}}catch(r){return{error:new u(o,`Failed to send email: ${r.message}`,{cause:r}),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{C as default};
@@ -1 +0,0 @@
1
- var E=Object.defineProperty;var y=(e,u)=>E(e,"name",{value:u,configurable:!0});import f from"./EmailError-zm2ffVav.js";import A from"./RequiredOptionError-CevW3u2K.js";import{c as k}from"./create-logger-BiWdqFNg.js";import{defineProvider as z}from"./defineProvider-CtLH0VBE.js";var P=Object.defineProperty,m=y((e,u)=>P(e,"name",{value:u,configurable:!0}),"f");const S=m(e=>typeof e=="function","isProviderFactory"),x=m(e=>e!=null&&typeof e=="object"&&"sendEmail"in e&&"initialize"in e&&"isAvailable"in e,"isProvider"),o="roundrobin",T=z((e={})=>{if(!e.mailers||e.mailers.length===0)throw new A(o,"mailers");const u={debug:e.debug??!1,logger:e.logger,mailers:e.mailers,retries:e.retries??3,retryAfter:e.retryAfter??60,timeout:e.timeout??3e4};let p=!1;const t=[];let a=0;const n=k(o,e.logger),v=m(async()=>{t.length=0;for(const i of u.mailers)try{let r;if(S(i))r=i({});else if(x(i))r=i;else{n.debug(`Skipping invalid mailer: ${i}`);continue}try{await r.initialize(),t.push(r),n.debug(`Initialized provider: ${r.name??"unknown"}`)}catch(s){n.debug(`Failed to initialize provider ${r.name??"unknown"}:`,s)}}catch(r){n.debug("Error processing mailer:",r)}if(t.length===0)throw new f(o,"No providers could be initialized");a=Math.floor(Math.random()*t.length),n.debug(`Round robin starting at index ${a} (${t[a]?.name??"unknown"})`)},"initializeProviders"),b=m(async()=>{if(t.length===0)return;const i=a;let r=0;for(;r<t.length;){const s=t[a];if(!s){a=(a+1)%t.length,r+=1;continue}const g=s.name??`provider-${a+1}`;try{if(await s.isAvailable())return a=(a+1)%t.length,n.debug(`Selected provider: ${g} (index ${a===0?t.length-1:a-1})`),s}catch{}a=(a+1)%t.length,r+=1,r<t.length&&u.retryAfter>0&&await new Promise(l=>{setTimeout(()=>{l(void 0)},u.retryAfter)})}return n.debug(`No available providers found, using provider at index ${i}`),t[i]??void 0},"getNextProvider");return{features:{attachments:!0,batchSending:!1,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!1,templates:!1,tracking:!1},async initialize(){if(!p)try{await v(),p=!0,n.debug(`Round robin provider initialized with ${t.length} provider(s)`)}catch(i){throw new f(o,`Failed to initialize: ${i.message}`,{cause:i})}},async isAvailable(){try{return t.length===0&&await v(),(await Promise.allSettled(t.map(i=>i.isAvailable()))).some(i=>i.status==="fulfilled"&&i.value)}catch{return!1}},name:o,options:u,async sendEmail(i){try{if(t.length===0&&await v(),t.length===0)return{error:new f(o,"No providers available"),success:!1};const r=await b();if(!r)return{error:new f(o,"No available providers found"),success:!1};const s=r.name??"unknown";n.debug(`Sending email via ${s}`);const g=await r.sendEmail(i);if(g.success)return n.debug(`Email sent successfully via ${s}`),{data:{...g.data,provider:`${o}(${s})`},success:!0};n.debug(`Failed to send via ${s}, trying next provider`);const l=[];g.error&&l.push(g.error);let w=0;for(;w<t.length-1;){const c=await b();if(!c||c===r)break;const h=c.name??"unknown";n.debug(`Retrying with ${h}`);try{const d=await c.sendEmail(i);if(d.success)return n.debug(`Email sent successfully via ${h} (after retry)`),{data:{...d.data,provider:`${o}(${h})`},success:!0};d.error&&l.push(d.error)}catch(d){l.push(d instanceof Error?d:new Error(String(d)))}w+=1}const $=l.map(c=>c instanceof Error?c.message:String(c)).join("; ");return{error:new f(o,`Failed to send email via all providers. Errors: ${$}`,{cause:l[0]instanceof Error?l[0]:new Error(String(l[0]))}),success:!1}}catch(r){return{error:new f(o,`Failed to send email: ${r.message}`,{cause:r}),success:!1}}},async validateCredentials(){return this.isAvailable()}}});export{T as default};