sently 0.8.0 → 0.9.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 (249) hide show
  1. package/AGENTS.md +34 -0
  2. package/CHANGELOG.md +33 -1
  3. package/README.md +89 -960
  4. package/dist/adapters/bun.js +1 -1
  5. package/dist/adapters/cf.js +2 -2
  6. package/dist/adapters/cf.js.map +2 -2
  7. package/dist/adapters/deno.js +1 -1
  8. package/dist/adapters/node.js +1 -1
  9. package/dist/auth/oauth2.js +2 -2
  10. package/dist/auth/oauth2.js.map +1 -1
  11. package/dist/chunk-0gqy32pe.js +4 -0
  12. package/dist/{chunk-n5dan5bz.js.map → chunk-0gqy32pe.js.map} +2 -2
  13. package/dist/{chunk-z62q7kqr.js → chunk-0qxws3kj.js} +2 -2
  14. package/dist/{chunk-z62q7kqr.js.map → chunk-0qxws3kj.js.map} +1 -1
  15. package/dist/chunk-29z7fkzy.js +4 -0
  16. package/dist/{chunk-wsymfbkn.js.map → chunk-29z7fkzy.js.map} +2 -2
  17. package/dist/chunk-5915vbcj.js +4 -0
  18. package/dist/chunk-5915vbcj.js.map +10 -0
  19. package/dist/{chunk-nbmq8ejv.js → chunk-6npp3x3c.js} +2 -2
  20. package/dist/{chunk-nbmq8ejv.js.map → chunk-6npp3x3c.js.map} +1 -1
  21. package/dist/chunk-7gy2q9sh.js +4 -0
  22. package/dist/chunk-7gy2q9sh.js.map +10 -0
  23. package/dist/{chunk-zst8an2p.js → chunk-8dty8c4p.js} +2 -2
  24. package/dist/{chunk-zst8an2p.js.map → chunk-8dty8c4p.js.map} +2 -2
  25. package/dist/{chunk-f3dz43cy.js → chunk-8njyga62.js} +2 -2
  26. package/dist/{chunk-f3dz43cy.js.map → chunk-8njyga62.js.map} +1 -1
  27. package/dist/chunk-93vqxxj2.js +4 -0
  28. package/dist/{chunk-qh90yr6d.js.map → chunk-93vqxxj2.js.map} +2 -2
  29. package/dist/chunk-b602dhck.js +4 -0
  30. package/dist/{chunk-wbvhaqcp.js.map → chunk-b602dhck.js.map} +2 -2
  31. package/dist/{chunk-9zkv5azm.js → chunk-gp4fwrs8.js} +2 -2
  32. package/dist/{chunk-9zkv5azm.js.map → chunk-gp4fwrs8.js.map} +1 -1
  33. package/dist/chunk-n0qeyzqm.js +13 -0
  34. package/dist/{chunk-8phpsns1.js.map → chunk-n0qeyzqm.js.map} +2 -2
  35. package/dist/chunk-qtkd5bak.js +4 -0
  36. package/dist/chunk-qtkd5bak.js.map +11 -0
  37. package/dist/chunk-skqhj0rm.js +4 -0
  38. package/dist/{chunk-vhxzhx8b.js.map → chunk-skqhj0rm.js.map} +2 -2
  39. package/dist/{chunk-6bkj4y4r.js → chunk-tamww15j.js} +2 -2
  40. package/dist/{chunk-6bkj4y4r.js.map → chunk-tamww15j.js.map} +1 -1
  41. package/dist/chunk-vjpds2pf.js +3 -0
  42. package/dist/{chunk-m07smapm.js.map → chunk-vjpds2pf.js.map} +2 -2
  43. package/dist/chunk-yp311efr.js +4 -0
  44. package/dist/chunk-yp311efr.js.map +10 -0
  45. package/dist/chunk-ywq6s10g.js +5 -0
  46. package/dist/{chunk-5scdgffb.js.map → chunk-ywq6s10g.js.map} +3 -3
  47. package/dist/core/base64.d.ts +10 -0
  48. package/dist/core/errors.js +2 -2
  49. package/dist/core/errors.js.map +1 -1
  50. package/dist/core/hooks.d.ts +14 -0
  51. package/dist/core/plugin.d.ts +7 -7
  52. package/dist/core/push-endpoint.d.ts +29 -0
  53. package/dist/core/push-types.d.ts +98 -0
  54. package/dist/core/sms-types.d.ts +76 -0
  55. package/dist/core/smtp.js +2 -2
  56. package/dist/core/smtp.js.map +1 -1
  57. package/dist/core/whatsapp-types.d.ts +116 -0
  58. package/dist/detect.js +2 -2
  59. package/dist/detect.js.map +1 -1
  60. package/dist/dkim.js +2 -2
  61. package/dist/dkim.js.map +2 -2
  62. package/dist/idempotency.js +2 -2
  63. package/dist/idempotency.js.map +1 -1
  64. package/dist/index.d.ts +5 -32
  65. package/dist/index.js +0 -35
  66. package/dist/mailer.js +2 -2
  67. package/dist/mailer.js.map +1 -1
  68. package/dist/observability/console.js +1 -1
  69. package/dist/plugins/react.js +2 -2
  70. package/dist/plugins/react.js.map +2 -2
  71. package/dist/plugins/template.js +1 -1
  72. package/dist/pool/pool.js +2 -2
  73. package/dist/pool/pool.js.map +1 -1
  74. package/dist/push.d.ts +26 -0
  75. package/dist/push.js +3 -0
  76. package/dist/push.js.map +10 -0
  77. package/dist/sms.d.ts +26 -0
  78. package/dist/sms.js +3 -0
  79. package/dist/sms.js.map +10 -0
  80. package/dist/smtp-mailer.js +2 -2
  81. package/dist/smtp-mailer.js.map +2 -2
  82. package/dist/transports/brevo.js +2 -2
  83. package/dist/transports/brevo.js.map +2 -2
  84. package/dist/transports/cloudflare-email.js +2 -2
  85. package/dist/transports/cloudflare-email.js.map +2 -2
  86. package/dist/transports/fallback.js +2 -2
  87. package/dist/transports/fallback.js.map +1 -1
  88. package/dist/transports/loops.js +2 -2
  89. package/dist/transports/loops.js.map +2 -2
  90. package/dist/transports/mailersend.js +2 -2
  91. package/dist/transports/mailersend.js.map +2 -2
  92. package/dist/transports/mailgun.js +2 -2
  93. package/dist/transports/mailgun.js.map +2 -2
  94. package/dist/transports/mailtrap.js +2 -2
  95. package/dist/transports/mailtrap.js.map +2 -2
  96. package/dist/transports/msegat.d.ts +132 -0
  97. package/dist/transports/msegat.js +3 -0
  98. package/dist/transports/msegat.js.map +10 -0
  99. package/dist/transports/plunk.js +2 -2
  100. package/dist/transports/plunk.js.map +2 -2
  101. package/dist/transports/postmark.js +2 -2
  102. package/dist/transports/postmark.js.map +2 -2
  103. package/dist/transports/preview.js +2 -2
  104. package/dist/transports/preview.js.map +2 -2
  105. package/dist/transports/resend.js +2 -2
  106. package/dist/transports/resend.js.map +2 -2
  107. package/dist/transports/retry.js +2 -2
  108. package/dist/transports/retry.js.map +2 -2
  109. package/dist/transports/sendgrid.js +2 -2
  110. package/dist/transports/sendgrid.js.map +2 -2
  111. package/dist/transports/ses.js +2 -2
  112. package/dist/transports/ses.js.map +2 -2
  113. package/dist/transports/smtp.js +2 -2
  114. package/dist/transports/smtp.js.map +1 -1
  115. package/dist/transports/sndr.d.ts +41 -0
  116. package/dist/transports/sndr.js +3 -0
  117. package/dist/transports/sndr.js.map +10 -0
  118. package/dist/transports/sparkpost.js +2 -2
  119. package/dist/transports/sparkpost.js.map +2 -2
  120. package/dist/transports/taqnyat-mail.d.ts +33 -0
  121. package/dist/transports/taqnyat-mail.js +3 -0
  122. package/dist/transports/taqnyat-mail.js.map +10 -0
  123. package/dist/transports/taqnyat-phone.d.ts +4 -0
  124. package/dist/transports/taqnyat-sms.d.ts +131 -0
  125. package/dist/transports/taqnyat-sms.js +3 -0
  126. package/dist/transports/taqnyat-sms.js.map +10 -0
  127. package/dist/transports/taqnyat-whatsapp.d.ts +51 -0
  128. package/dist/transports/taqnyat-whatsapp.js +3 -0
  129. package/dist/transports/taqnyat-whatsapp.js.map +10 -0
  130. package/dist/transports/twilio-sms.d.ts +66 -0
  131. package/dist/transports/twilio-sms.js +3 -0
  132. package/dist/transports/twilio-sms.js.map +10 -0
  133. package/dist/transports/webpush.d.ts +46 -0
  134. package/dist/transports/webpush.js +3 -0
  135. package/dist/transports/webpush.js.map +10 -0
  136. package/dist/transports/weighted-fallback.js +2 -2
  137. package/dist/transports/weighted-fallback.js.map +1 -1
  138. package/dist/transports/whatsapp-cloud.d.ts +56 -0
  139. package/dist/transports/whatsapp-cloud.js +3 -0
  140. package/dist/transports/whatsapp-cloud.js.map +10 -0
  141. package/dist/webhooks/brevo.js +1 -1
  142. package/dist/webhooks/mailgun.js +1 -1
  143. package/dist/webhooks/postmark.js +1 -1
  144. package/dist/webhooks/resend.js +1 -1
  145. package/dist/webhooks/sendgrid.js +1 -1
  146. package/dist/webhooks/ses.js +1 -1
  147. package/dist/webhooks/sndr.d.ts +16 -0
  148. package/dist/webhooks/sndr.js +3 -0
  149. package/dist/webhooks/sndr.js.map +10 -0
  150. package/dist/webhooks/timing-safe-equal.js +1 -1
  151. package/dist/webhooks.d.ts +9 -2
  152. package/dist/webhooks.js +4 -0
  153. package/dist/whatsapp.d.ts +26 -0
  154. package/dist/whatsapp.js +3 -0
  155. package/dist/whatsapp.js.map +10 -0
  156. package/package.json +85 -4
  157. package/site/README.md +26 -0
  158. package/site/content/docs/ai/index.mdx +13 -0
  159. package/site/content/docs/ai/llms-txt.mdx +12 -0
  160. package/site/content/docs/ai/mcp.mdx +16 -0
  161. package/site/content/docs/ai/meta.json +9 -0
  162. package/site/content/docs/channels/email.mdx +100 -0
  163. package/site/content/docs/channels/hooks.mdx +87 -0
  164. package/site/content/docs/channels/index.mdx +81 -0
  165. package/site/content/docs/channels/meta.json +12 -0
  166. package/site/content/docs/channels/push.mdx +92 -0
  167. package/site/content/docs/channels/sms.mdx +88 -0
  168. package/site/content/docs/channels/whatsapp.mdx +95 -0
  169. package/site/content/docs/decorators/fallback.mdx +34 -0
  170. package/site/content/docs/decorators/idempotency.mdx +34 -0
  171. package/site/content/docs/decorators/index.mdx +50 -0
  172. package/site/content/docs/decorators/meta.json +12 -0
  173. package/site/content/docs/decorators/preview.mdx +34 -0
  174. package/site/content/docs/decorators/retry.mdx +34 -0
  175. package/site/content/docs/decorators/weighted-fallback.mdx +34 -0
  176. package/site/content/docs/get-started/entrypoints.mdx +114 -0
  177. package/site/content/docs/get-started/index.mdx +14 -0
  178. package/site/content/docs/get-started/installation.mdx +78 -0
  179. package/site/content/docs/get-started/introduction.mdx +77 -0
  180. package/site/content/docs/get-started/meta.json +12 -0
  181. package/site/content/docs/get-started/migrate-nodemailer.mdx +23 -0
  182. package/site/content/docs/get-started/runtimes.mdx +17 -0
  183. package/site/content/docs/guides/adapters.mdx +26 -0
  184. package/site/content/docs/guides/attachments.mdx +19 -0
  185. package/site/content/docs/guides/bundle-size.mdx +78 -0
  186. package/site/content/docs/guides/dkim.mdx +26 -0
  187. package/site/content/docs/guides/index.mdx +15 -0
  188. package/site/content/docs/guides/meta.json +21 -0
  189. package/site/content/docs/guides/oauth2.mdx +23 -0
  190. package/site/content/docs/guides/observability.mdx +16 -0
  191. package/site/content/docs/guides/plugins-template.mdx +22 -0
  192. package/site/content/docs/guides/pool.mdx +24 -0
  193. package/site/content/docs/guides/react-email.mdx +20 -0
  194. package/site/content/docs/guides/security.mdx +17 -0
  195. package/site/content/docs/guides/send-bulk.mdx +18 -0
  196. package/site/content/docs/guides/vendor-extras-otp.mdx +18 -0
  197. package/site/content/docs/guides/webhooks.mdx +120 -0
  198. package/site/content/docs/guides/webpush-interop.mdx +18 -0
  199. package/site/content/docs/index.mdx +15 -0
  200. package/site/content/docs/meta.json +15 -0
  201. package/site/content/docs/quick-start/email.mdx +40 -0
  202. package/site/content/docs/quick-start/index.mdx +15 -0
  203. package/site/content/docs/quick-start/meta.json +5 -0
  204. package/site/content/docs/quick-start/push.mdx +36 -0
  205. package/site/content/docs/quick-start/sms.mdx +36 -0
  206. package/site/content/docs/quick-start/whatsapp.mdx +38 -0
  207. package/site/content/docs/reference/errors.mdx +22 -0
  208. package/site/content/docs/reference/exports.mdx +83 -0
  209. package/site/content/docs/reference/index.mdx +15 -0
  210. package/site/content/docs/reference/mail-options.mdx +22 -0
  211. package/site/content/docs/reference/meta.json +15 -0
  212. package/site/content/docs/reference/push-options.mdx +20 -0
  213. package/site/content/docs/reference/sms-options.mdx +17 -0
  214. package/site/content/docs/reference/transport-contracts.mdx +17 -0
  215. package/site/content/docs/reference/webhook-events.mdx +70 -0
  216. package/site/content/docs/reference/whatsapp-options.mdx +15 -0
  217. package/site/content/docs/transports/brevo.mdx +40 -0
  218. package/site/content/docs/transports/cloudflare-email.mdx +40 -0
  219. package/site/content/docs/transports/index.mdx +105 -0
  220. package/site/content/docs/transports/loops.mdx +41 -0
  221. package/site/content/docs/transports/mailersend.mdx +40 -0
  222. package/site/content/docs/transports/mailgun.mdx +42 -0
  223. package/site/content/docs/transports/mailtrap.mdx +42 -0
  224. package/site/content/docs/transports/meta.json +32 -0
  225. package/site/content/docs/transports/msegat.mdx +42 -0
  226. package/site/content/docs/transports/plunk.mdx +40 -0
  227. package/site/content/docs/transports/postmark.mdx +40 -0
  228. package/site/content/docs/transports/resend.mdx +43 -0
  229. package/site/content/docs/transports/sendgrid.mdx +40 -0
  230. package/site/content/docs/transports/ses.mdx +44 -0
  231. package/site/content/docs/transports/smtp.mdx +47 -0
  232. package/site/content/docs/transports/sndr.mdx +144 -0
  233. package/site/content/docs/transports/sparkpost.mdx +41 -0
  234. package/site/content/docs/transports/taqnyat-mail.mdx +41 -0
  235. package/site/content/docs/transports/taqnyat-sms.mdx +41 -0
  236. package/site/content/docs/transports/taqnyat-whatsapp.mdx +40 -0
  237. package/site/content/docs/transports/twilio-sms.mdx +128 -0
  238. package/site/content/docs/transports/webpush.mdx +43 -0
  239. package/site/content/docs/transports/whatsapp-cloud.mdx +42 -0
  240. package/dist/chunk-5scdgffb.js +0 -5
  241. package/dist/chunk-8phpsns1.js +0 -13
  242. package/dist/chunk-m07smapm.js +0 -3
  243. package/dist/chunk-n5dan5bz.js +0 -4
  244. package/dist/chunk-qh90yr6d.js +0 -4
  245. package/dist/chunk-vhxzhx8b.js +0 -4
  246. package/dist/chunk-wbvhaqcp.js +0 -4
  247. package/dist/chunk-wsymfbkn.js +0 -4
  248. package/dist/chunk-yr56b0ts.js +0 -4
  249. package/dist/chunk-yr56b0ts.js.map +0 -11
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: SMTP
3
+ description: Connect to an SMTP relay with an explicit transport.
4
+ icon: Truck
5
+ source: "src/transports/smtp.ts"
6
+ ---
7
+
8
+ Connect to an SMTP relay with an explicit transport.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `host` | `string` | required |
17
+ | `port` | `number` | 587 or 465 based on secure |
18
+ | `secure` | `boolean` | false |
19
+ | `auth` | `SMTPAuth` | optional |
20
+ | `requireTLS` | `boolean` | true when auth is set |
21
+ | `adapter` | `SocketAdapter` | optional |
22
+ | `pool` | `boolean` | false |
23
+ | `dkim` | `DKIMConfig` | optional |
24
+
25
+ <Steps>
26
+ <Step title="Configure the transport">
27
+
28
+ ```ts
29
+ import { createMailer } from "sently/mailer";
30
+ import { SMTPTransport } from "sently/transports/smtp";
31
+
32
+ const sender = createMailer({ transport: new SMTPTransport({ host: "smtp.example.com", auth: { user: "user@example.com", pass: "..." } }) });
33
+ ```
34
+
35
+ </Step>
36
+ <Step title="Send with the channel API">
37
+
38
+ ```ts
39
+ await sender.send({ from: "hello@example.com", to: "person@example.com", subject: "Hello", text: "Hi" });
40
+ ```
41
+
42
+ </Step>
43
+ </Steps>
44
+
45
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
46
+
47
+ <Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
@@ -0,0 +1,144 @@
1
+ ---
2
+ title: SNDR
3
+ description: Send transactional email through the SNDR HTTP API with createMailer.
4
+ icon: Truck
5
+ source: "src/transports/sndr.ts"
6
+ ---
7
+
8
+ Use SNDR when you want an HTTP email API for receipts, sign-in mail, and other app-triggered messages.
9
+ Wire `SndrTransport` into `createMailer` — do not call the vendor SDK from application code.
10
+
11
+ <Callout title="The one rule">
12
+ Import from `sently/transports/sndr`, pass the transport to `await createMailer(...)`, and send
13
+ with the mailer — keep delivery on the email channel.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Configure
22
+
23
+ ```ts
24
+ import { createMailer } from "sently/mailer";
25
+ import { SndrTransport } from "sently/transports/sndr";
26
+
27
+ const mailer = await createMailer({
28
+ transport: new SndrTransport({
29
+ apiKey: process.env.SNDR_API_KEY!,
30
+ }),
31
+ });
32
+ ```
33
+
34
+ </Step>
35
+
36
+ <Step>
37
+ ### Send
38
+
39
+ ```ts
40
+ const result = await mailer.send({
41
+ from: "hello@yourdomain.com",
42
+ to: "customer@example.com",
43
+ subject: "Welcome aboard",
44
+ html: "<p>Thanks for joining us.</p>",
45
+ });
46
+ ```
47
+
48
+ </Step>
49
+
50
+ <Step>
51
+ ### See the result
52
+
53
+ ```ts
54
+ console.log(result.messageId); // e.g. em_…
55
+ console.log(result.response); // e.g. queued
56
+ ```
57
+
58
+ `from` must use a domain verified in the SNDR dashboard.
59
+
60
+ </Step>
61
+
62
+ </Steps>
63
+
64
+ ## Configuration
65
+
66
+ | Option | Type | Default | Meaning |
67
+ | ------ | ---- | ------- | ------- |
68
+ | `apiKey` | `string` | required | Bearer key (`sndr_live_…` / `sndr_test_…`). |
69
+ | `baseUrl` | `string` | `https://api.sndr.sh` | API origin (no trailing slash needed). |
70
+ | `defaultTemplateId` | `string` | — | Default SNDR `template_id` when not set per message. |
71
+
72
+ ## Message mapping
73
+
74
+ | Mail option | SNDR field | Notes |
75
+ | ----------- | ---------- | ----- |
76
+ | `from` | `from` | MIME form `Name <addr>` when a display name is present. |
77
+ | `to` / `cc` / `bcc` | `to` / `cc` / `bcc` | Arrays of email addresses. |
78
+ | `replyTo` | `reply_to` | First address only. |
79
+ | `subject` | `subject` | Required. |
80
+ | `html` / `text` | `html` / `text` | Optional body fields. |
81
+ | `headers` | `headers` | Custom headers; template header is stripped. |
82
+ | `idempotencyKey` / `messageId` | `Idempotency-Key` | Sent when present. |
83
+ | `data` | `variables` | Only when a template id is set. |
84
+
85
+ ## Templates
86
+
87
+ Set a template with the `x-sndr-template-id` header (or `defaultTemplateId` on the transport).
88
+ Pass template variables through `data`.
89
+
90
+ ```ts
91
+ import { SNDR_TEMPLATE_ID_HEADER } from "sently/transports/sndr";
92
+
93
+ await mailer.send({
94
+ from: "hello@yourdomain.com",
95
+ to: "customer@example.com",
96
+ subject: "Welcome",
97
+ headers: { [SNDR_TEMPLATE_ID_HEADER]: "tpl_welcome" },
98
+ data: { name: "Ada" },
99
+ });
100
+ ```
101
+
102
+ ## Verify and errors
103
+
104
+ `verify()` calls `GET /v1/domains` with the same Bearer key.
105
+
106
+ Failed HTTP responses throw `SndrError` (`SentlyError`) with the API `error.message` when present.
107
+
108
+ ## Troubleshooting
109
+
110
+ <Accordions>
111
+
112
+ <Accordion title="from domain is not verified">
113
+
114
+ SNDR rejects sends from unverified domains. Verify DNS in the SNDR dashboard, then retry with that domain in `from`.
115
+
116
+ </Accordion>
117
+
118
+ <Accordion title="Should I import SndrTransport from sently?">
119
+
120
+ No. Use `sently/transports/sndr`. The main `sently` package does not re-export HTTP provider transports.
121
+
122
+ </Accordion>
123
+
124
+ <Accordion title="Should I call the @rkiza/sndr SDK?">
125
+
126
+ No. Use `createMailer` plus `SndrTransport`. Keep vendor extras off the shared email contract.
127
+
128
+ </Accordion>
129
+
130
+ </Accordions>
131
+
132
+ ## Learn more
133
+
134
+ - [Email channel](/docs/channels/email) — mailer options and send pipeline
135
+ - [Webhooks](/docs/guides/webhooks) — SNDR delivery events and signature verification
136
+ - [Bundle size](/docs/guides/bundle-size) — why subpath imports stay small
137
+
138
+ ## Next
139
+
140
+ <Cards>
141
+ <Card title="Email channel" description="createMailer and MailOptions." href="/docs/channels/email" />
142
+ <Card title="Webhooks" description="Parse and verify SNDR events." href="/docs/guides/webhooks" />
143
+ <Card title="Transports" description="All provider transports by channel." href="/docs/transports" />
144
+ </Cards>
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: SparkPost
3
+ description: Send email through the SparkPost transmissions API.
4
+ icon: Truck
5
+ source: "src/transports/sparkpost.ts"
6
+ ---
7
+
8
+ Send email through the SparkPost transmissions API.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `apiKey` | `string` | required |
17
+ | `euRegion` | `boolean` | false |
18
+
19
+ <Steps>
20
+ <Step title="Configure the transport">
21
+
22
+ ```ts
23
+ import { createMailer } from "sently/mailer";
24
+ import { SparkPostTransport } from "sently/transports/sparkpost";
25
+
26
+ const sender = createMailer({ transport: new SparkPostTransport({ apiKey: process.env.SPARKPOST_API_KEY! }) });
27
+ ```
28
+
29
+ </Step>
30
+ <Step title="Send with the channel API">
31
+
32
+ ```ts
33
+ await sender.send({ from: "hello@example.com", to: "person@example.com", subject: "Hello", text: "Hi" });
34
+ ```
35
+
36
+ </Step>
37
+ </Steps>
38
+
39
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
40
+
41
+ <Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: Taqnyat Mail
3
+ description: Send email through the Taqnyat Email API.
4
+ icon: Truck
5
+ source: "src/transports/taqnyat-mail.ts"
6
+ ---
7
+
8
+ Send email through the Taqnyat Email API.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `bearerToken` | `string` | required |
17
+ | `campaignName` | `string` | required |
18
+
19
+ <Steps>
20
+ <Step title="Configure the transport">
21
+
22
+ ```ts
23
+ import { createMailer } from "sently/mailer";
24
+ import { TaqnyatMailTransport } from "sently/transports/taqnyat-mail";
25
+
26
+ const sender = createMailer({ transport: new TaqnyatMailTransport({ bearerToken: process.env.TAQNYAT_MAIL_TOKEN!, campaignName: "transactional" }) });
27
+ ```
28
+
29
+ </Step>
30
+ <Step title="Send with the channel API">
31
+
32
+ ```ts
33
+ await sender.send({ from: "hello@example.com", to: "person@example.com", subject: "Hello", text: "Hi" });
34
+ ```
35
+
36
+ </Step>
37
+ </Steps>
38
+
39
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
40
+
41
+ <Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: Taqnyat SMS
3
+ description: Send SMS through the Taqnyat API.
4
+ icon: Truck
5
+ source: "src/transports/taqnyat-sms.ts"
6
+ ---
7
+
8
+ Send SMS through the Taqnyat API.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `bearerToken` | `string` | required |
17
+ | `sender` | `string` | required |
18
+
19
+ <Steps>
20
+ <Step title="Configure the transport">
21
+
22
+ ```ts
23
+ import { createSmsSender } from "sently/sms";
24
+ import { TaqnyatSmsTransport } from "sently/transports/taqnyat-sms";
25
+
26
+ const sender = createSmsSender({ transport: new TaqnyatSmsTransport({ bearerToken: "...", sender: "MyBrand" }) });
27
+ ```
28
+
29
+ </Step>
30
+ <Step title="Send with the channel API">
31
+
32
+ ```ts
33
+ await sender.send({ to: "+15551234567", body: "Hello" });
34
+ ```
35
+
36
+ </Step>
37
+ </Steps>
38
+
39
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
40
+
41
+ <Cards><Card title="Sms channel" href="/docs/channels/sms" /></Cards>
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Taqnyat WhatsApp
3
+ description: Send WhatsApp templates and session text through Taqnyat.
4
+ icon: Truck
5
+ source: "src/transports/taqnyat-whatsapp.ts"
6
+ ---
7
+
8
+ Send WhatsApp templates and session text through Taqnyat.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `bearerToken` | `string` | required |
17
+
18
+ <Steps>
19
+ <Step title="Configure the transport">
20
+
21
+ ```ts
22
+ import { createWhatsAppSender } from "sently/whatsapp";
23
+ import { TaqnyatWhatsAppTransport } from "sently/transports/taqnyat-whatsapp";
24
+
25
+ const sender = createWhatsAppSender({ transport: new TaqnyatWhatsAppTransport({ bearerToken: "..." }) });
26
+ ```
27
+
28
+ </Step>
29
+ <Step title="Send with the channel API">
30
+
31
+ ```ts
32
+ await sender.send({ to: "15551234567", template: { name: "welcome", language: "en_US" } });
33
+ ```
34
+
35
+ </Step>
36
+ </Steps>
37
+
38
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
39
+
40
+ <Cards><Card title="Whatsapp channel" href="/docs/channels/whatsapp" /></Cards>
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: Twilio SMS
3
+ description: Send SMS through the Twilio Messages API with createSmsSender.
4
+ icon: Truck
5
+ source: "src/transports/twilio-sms.ts"
6
+ ---
7
+
8
+ Use Twilio when your SMS traffic should go through the Programmable Messaging Messages API.
9
+ Wire `TwilioSmsTransport` into `createSmsSender` — Verify, Voice, and other Twilio products stay off this transport.
10
+
11
+ <Callout title="The one rule">
12
+ Provide `From` **or** `messagingServiceSid` (or both), keep E.164 `+` prefixes, and send only
13
+ through `createSmsSender`.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Configure
22
+
23
+ ```ts
24
+ import { createSmsSender } from "sently/sms";
25
+ import { TwilioSmsTransport } from "sently/transports/twilio-sms";
26
+
27
+ const sms = createSmsSender({
28
+ transport: new TwilioSmsTransport({
29
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
30
+ apiKey: process.env.TWILIO_API_KEY!,
31
+ authToken: process.env.TWILIO_API_KEY_SECRET!,
32
+ from: "+15557654321",
33
+ }),
34
+ });
35
+ ```
36
+
37
+ Production auth: API Key SID as Basic username and API Key Secret as password, with Account SID still in the URL path.
38
+ For local testing you may omit `apiKey` and use Account SID + Auth Token.
39
+
40
+ </Step>
41
+
42
+ <Step>
43
+ ### Send
44
+
45
+ ```ts
46
+ const result = await sms.send({
47
+ to: "+15551234567",
48
+ body: "Your code is 123456",
49
+ });
50
+ ```
51
+
52
+ </Step>
53
+
54
+ <Step>
55
+ ### See the result
56
+
57
+ ```ts
58
+ console.log(result.messageId); // SM…
59
+ console.log(result.status); // e.g. queued
60
+ ```
61
+
62
+ </Step>
63
+
64
+ </Steps>
65
+
66
+ ## Configuration
67
+
68
+ | Option | Type | Default | Meaning |
69
+ | ------ | ---- | ------- | ------- |
70
+ | `accountSid` | `string` | required | Account SID in the Messages URL path. |
71
+ | `authToken` | `string` | required | Auth Token, or API Key Secret when `apiKey` is set. |
72
+ | `apiKey` | `string` | — | API Key SID used as Basic-auth username (recommended in production). |
73
+ | `from` | `string` | — | Default From number / sender id. |
74
+ | `messagingServiceSid` | `string` | — | Messaging Service SID (`MG…`) as sender alternative. |
75
+ | `statusCallback` | `string` | — | Delivery status callback URL. |
76
+
77
+ Either `from` / `options.from` or `messagingServiceSid` is required for each send.
78
+
79
+ ## Messaging Service
80
+
81
+ When `messagingServiceSid` is set and `From` is omitted, Twilio picks a sender from the service pool.
82
+ You may set both to pin a specific From inside that pool.
83
+
84
+ ```ts
85
+ const sms = createSmsSender({
86
+ transport: new TwilioSmsTransport({
87
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
88
+ authToken: process.env.TWILIO_AUTH_TOKEN!,
89
+ messagingServiceSid: "MG…",
90
+ }),
91
+ });
92
+ ```
93
+
94
+ ## Verify and errors
95
+
96
+ `verify()` fetches `GET /2010-04-01/Accounts/{AccountSid}.json` with the same Basic credentials.
97
+
98
+ Failed Messages responses throw `TwilioSmsError` with Twilio’s `message` / `code` payload when present.
99
+
100
+ ## Troubleshooting
101
+
102
+ <Accordions>
103
+
104
+ <Accordion title="Twilio SMS requires From or MessagingServiceSid">
105
+
106
+ Set `from` on the transport or per send, or configure `messagingServiceSid`.
107
+
108
+ </Accordion>
109
+
110
+ <Accordion title="Invalid To phone number">
111
+
112
+ Twilio expects E.164 with a leading `+`. Do not strip `+` the way some regional SMS APIs require.
113
+
114
+ </Accordion>
115
+
116
+ </Accordions>
117
+
118
+ ## Learn more
119
+
120
+ - [SMS channel](/docs/channels/sms) — `createSmsSender` and hooks
121
+ - [Transports](/docs/transports) — other SMS providers
122
+
123
+ ## Next
124
+
125
+ <Cards>
126
+ <Card title="SMS channel" description="createSmsSender options." href="/docs/channels/sms" />
127
+ <Card title="Transports" description="Providers by channel." href="/docs/transports" />
128
+ </Cards>
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Web Push
3
+ description: Encrypt and send browser notifications with VAPID.
4
+ icon: Truck
5
+ source: "src/transports/webpush.ts"
6
+ ---
7
+
8
+ Encrypt and send browser notifications with VAPID.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `vapidPublicKey` | `string` | required |
17
+ | `vapidPrivateKey` | `string` | required |
18
+ | `subject` | `string` | required |
19
+ | `allowedEndpointHosts` | `string[]` | optional |
20
+
21
+ <Steps>
22
+ <Step title="Configure the transport">
23
+
24
+ ```ts
25
+ import { createPushSender } from "sently/push";
26
+ import { WebPushTransport } from "sently/transports/webpush";
27
+
28
+ const sender = createPushSender({ transport: new WebPushTransport({ vapidPublicKey: "...", vapidPrivateKey: "...", subject: "mailto:you@example.com" }) });
29
+ ```
30
+
31
+ </Step>
32
+ <Step title="Send with the channel API">
33
+
34
+ ```ts
35
+ await sender.send({ subscription, title: "Hello", body: "World" });
36
+ ```
37
+
38
+ </Step>
39
+ </Steps>
40
+
41
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
42
+
43
+ <Cards><Card title="Push channel" href="/docs/channels/push" /></Cards>
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: WhatsApp Cloud
3
+ description: Send WhatsApp templates and session text through Meta Cloud API.
4
+ icon: Truck
5
+ source: "src/transports/whatsapp-cloud.ts"
6
+ ---
7
+
8
+ Send WhatsApp templates and session text through Meta Cloud API.
9
+
10
+ <Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
11
+
12
+ ## Configuration
13
+
14
+ | Option | Type | Default or requirement |
15
+ | --- | --- | --- |
16
+ | `accessToken` | `string` | required |
17
+ | `phoneNumberId` | `string` | required |
18
+ | `apiVersion` | `string` | v26.0 |
19
+
20
+ <Steps>
21
+ <Step title="Configure the transport">
22
+
23
+ ```ts
24
+ import { createWhatsAppSender } from "sently/whatsapp";
25
+ import { WhatsAppCloudTransport } from "sently/transports/whatsapp-cloud";
26
+
27
+ const sender = createWhatsAppSender({ transport: new WhatsAppCloudTransport({ accessToken: "...", phoneNumberId: "..." }) });
28
+ ```
29
+
30
+ </Step>
31
+ <Step title="Send with the channel API">
32
+
33
+ ```ts
34
+ await sender.send({ to: "15551234567", template: { name: "welcome", language: "en_US" } });
35
+ ```
36
+
37
+ </Step>
38
+ </Steps>
39
+
40
+ <Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
41
+
42
+ <Cards><Card title="Whatsapp channel" href="/docs/channels/whatsapp" /></Cards>
@@ -1,5 +0,0 @@
1
- var J=new TextEncoder;function P(k){let f=typeof k==="string"?J.encode(k):k,j="",A=0;while(A<f.length){let m=f[A]??0,q=f[A+1],z=f[A+2];if(q===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4],j+="==";break}if(z===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2],j+="=";break}j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2|z>>6],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[z&63],A+=3}return R(j)}function S(k){let f=k.replace(/\s/g,""),j=f.length;if(j===0)return new Uint8Array(0);if(j%4!==0)throw Error("Invalid base64 string length");let A=f.endsWith("==")?2:f.endsWith("=")?1:0,m=j*3/4-A,q=new Uint8Array(m),z=0;for(let D=0;D<j;D+=4){let K=G(f[D]??"="),M=G(f[D+1]??"="),N=G(f[D+2]??"="),O=G(f[D+3]??"="),H=K<<18|M<<12|N<<6|O;if(z<m)q[z++]=H>>16&255;if(z<m)q[z++]=H>>8&255;if(z<m)q[z++]=H&255}return q}function W(k){if(!Q(k))return k;return`=?UTF-8?B?${P(k).replace(/\r\n/g,"")}?=`}function Q(k){for(let f=0;f<k.length;f++)if(k.charCodeAt(f)>127)return!0;return!1}function G(k){if(k==="=")return 0;let f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(k);if(f===-1)throw Error(`Invalid base64 character: ${k}`);return f}function R(k){let f=[];for(let j=0;j<k.length;j+=76)f.push(k.slice(j,j+76));return f.join(`\r
2
- `)}function X(k){return J.encode(k)}
3
- export{P as T,S as U,W as V,X as W};
4
-
5
- //# debugId=4A06DB0A7917D84164756E2164756E21
@@ -1,13 +0,0 @@
1
- import{T as O,U as K,W as H}from"./chunk-5scdgffb.js";import{X as v,Z as B}from"./chunk-6bkj4y4r.js";var j=64;function X(q){return q>>>0}function U(q){let z=g(q),G=1732584193,Q=4023233417,N=2562383102,J=271733878;for(let F=0;F<z.length;F+=64){let $=z.subarray(F,F+64),w=new Uint32Array(16);for(let W=0;W<16;W++){let A=W*4;w[W]=X(($[A]??0)|($[A+1]??0)<<8|($[A+2]??0)<<16|($[A+3]??0)<<24)}let x=G,D=Q,I=N,R=J;for(let W=0;W<64;W++){let A,_;if(W<16)A=X(D&I|~D&R),_=W;else if(W<32)A=X(D&R|I&~R),_=X((5*W+1)%16);else if(W<48)A=X(D^I^R),_=X((3*W+5)%16);else A=X(I^(D|~R)),_=X(7*W%16);let y=R;R=I,I=D,D=X(D+T(X(x+A+X((P[W]??0)+(w[_]??0))),L[W]??0)),x=y}G=X(G+x),Q=X(Q+D),N=X(N+I),J=X(J+R)}let Y=new Uint8Array(16),V=new DataView(Y.buffer);return V.setUint32(0,G,!0),V.setUint32(4,Q,!0),V.setUint32(8,N,!0),V.setUint32(12,J,!0),Y}var L=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],P=new Uint32Array([3614090360,3905402710,606105819,3250441966,4118548399,1200080426,2821735955,4249261313,1770035416,2336552879,4294925233,2304563134,1804603682,4254626195,2792965006,1236535329,4129170786,3225465664,643717713,3921069994,3593408605,38016083,3634488961,3889429448,568446438,3275163606,4107603335,1163531501,2850285829,4243563512,1735328473,2368359562,4294588738,2272392833,1839030562,4259657740,2763975236,1272893353,4139469664,3200236656,681279174,3936430074,3572445317,76029189,3654602809,3873151461,530742520,3299628645,4096336452,1126891415,2878612391,4237533241,1700485571,2399980690,4293915773,2240044497,1873313359,4264355552,2734768916,1309151649,4149444226,3174756917,718787259,3951481745]);function T(q,z){return X(q<<z|q>>>32-z)}function g(q){let z=q.length*8,G=(56-(q.length+1)%64+64)%64,Q=q.length+1+G+8,N=new Uint8Array(Q);N.set(q),N[q.length]=128;let J=new DataView(N.buffer);return J.setUint32(Q-8,z>>>0,!0),J.setUint32(Q-4,Math.floor(z/4294967296),!0),N}function C(q,z){let G=q;if(G.length>j)G=U(G);let Q=new Uint8Array(j);Q.set(G);let N=new Uint8Array(j),J=new Uint8Array(j);for(let $=0;$<j;$++)N[$]=(Q[$]??0)^54,J[$]=(Q[$]??0)^92;let Y=new Uint8Array(N.length+z.length);Y.set(N),Y.set(z,N.length);let V=U(Y),F=new Uint8Array(J.length+V.length);return F.set(J),F.set(V,J.length),U(F)}function E(q){return Array.from(q,(z)=>z.toString(16).padStart(2,"0")).join("")}async function M(q,z,G){let Q=K(q.trim()),N=H(G),J=C(N,Q),Y=E(J);return O(`${z} ${Y}`).replace(/\r\n/g,"")}class Z extends v{command;response;constructor(q,z,G,Q){super(q,B(z,G),{provider:"smtp"});this.command=G;this.response=Q;this.name="SMTPError",this.code=z}}function u(q){let z;switch(q.type){case"EHLO":{z=`EHLO ${q.domain.replace(/[\r\n]/g,"")}`;break}case"STARTTLS":z="STARTTLS";break;case"AUTH_LOGIN":z="AUTH LOGIN";break;case"AUTH_PLAIN":z=`AUTH PLAIN ${O(`\x00${q.user}\x00${q.pass}`).replace(/\r\n/g,"")}`;break;case"AUTH_CRAM_MD5_INIT":z="AUTH CRAM-MD5";break;case"AUTH_CRAM_MD5_RESPONSE":return H(`${q.response}\r
2
- `);case"AUTH_XOAUTH2":z=`AUTH XOAUTH2 ${q.xoauth2String}`;break;case"MAIL_FROM":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"MAIL FROM",q.address);z=`MAIL FROM:<${q.address}>`;break;case"RCPT_TO":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"RCPT TO",q.address);z=`RCPT TO:<${q.address}>`;break;case"DATA":z="DATA";break;case"DATA_BODY":return H(S(q.content));case"QUIT":z="QUIT";break;case"RSET":z="RSET";break;case"NOOP":z="NOOP";break}return H(`${z}\r
3
- `)}function l(q){let z=new TextDecoder().decode(q).trim(),G=z.split(/\r?\n/),N=(G[G.length-1]??"").match(/^(\d{3})([\s-])(.*)$/);if(!N)throw new Z("Invalid SMTP response",0,"PARSE",z);let J=Number.parseInt(N[1]??"0",10),Y=G.map((V)=>V.replace(/^\d{3}[\s-]/,"")).join(" ");return{code:J,message:Y,isSuccess:J>=200&&J<300,isReady:J>=300&&J<400,isError:J>=400}}function p(q){if(q.length===0)return null;let z=q.reduce((V,F)=>V+F.length,0),G=new Uint8Array(z),Q=0;for(let V of q)G.set(V,Q),Q+=V.length;let J=new TextDecoder().decode(G).split(/\r?\n/).filter((V)=>V.length>0);if(J.length===0)return null;let Y=J[J.length-1]??"";if(/^\d{3} /.test(Y))return G;return null}function m(q){let z=q.map((G)=>G.toUpperCase());if(z.some((G)=>G.includes("AUTH")&&G.includes("XOAUTH2")))return"OAUTH2";if(z.some((G)=>G.includes("AUTH")&&G.includes("CRAM-MD5")))return"CRAM-MD5";if(z.some((G)=>G.includes("AUTH")&&G.includes("LOGIN")))return"LOGIN";if(z.some((G)=>G.includes("AUTH")&&G.includes("PLAIN")))return"PLAIN";throw new Z("No supported AUTH method",0,"EHLO",q.join(" "))}function i(q){return q.message.split(/\s+/).flatMap((z)=>z.split(/\r?\n/)).filter(Boolean)}function r(q,z,G){if(!z.includes(q.code))throw new Z(`Unexpected SMTP response for ${G}`,q.code,G,q.message)}function S(q){return`${new TextDecoder().decode(q).split(/\r?\n/).map((N)=>N.startsWith(".")?`.${N}`:N).join(`\r
4
- `)}\r
5
- .\r
6
- `}function t(q){return H(`${O(q).replace(/\r\n/g,"")}\r
7
- `)}function o(q){return H(`${O(q).replace(/\r\n/g,"")}\r
8
- `)}function n(q){return H(`${q}\r
9
- `)}function s(q){return H(`${q}\r
10
- `)}
11
- export{M as H,Z as I,u as J,l as K,p as L,m as M,i as N,r as O,t as P,o as Q,n as R,s as S};
12
-
13
- //# debugId=B24DD712C817A87F64756E2164756E21
@@ -1,3 +0,0 @@
1
- import{s as U}from"./chunk-wbvhaqcp.js";import{u as X}from"./chunk-zst8an2p.js";import{v as b}from"./chunk-vhxzhx8b.js";import{H as k,I as $,J as Z,K as O,L as q,M as L,N as v,O as N,P as A,Q as j,S as E}from"./chunk-8phpsns1.js";import{_ as C}from"./chunk-nbmq8ejv.js";class T{provider="smtp";config;adapter=null;constructor(z){this.config=P(z)}async send(z){let B={...z,attachments:await b(z.attachments)},G=await X(B,this.config.dkim),F=await this.getAdapter(),W=this.config.direct?await M(G.envelope.from.split("@")[1]??this.config.host):this.config.host;await F.connect(W,this.config.port),this.adapter=F;try{return await D(F,this.config),await S(F,G)}finally{await H(F),this.adapter=null}}async verify(){try{let z=await this.getAdapter();await z.connect(this.config.host,this.config.port);try{return await D(z,this.config),{ok:!0,provider:"smtp"}}finally{await H(z)}}catch(z){return{ok:!1,provider:"smtp",message:z instanceof Error?z.message:String(z)}}}async close(){if(this.adapter)await this.adapter.close(),this.adapter=null}async getAdapter(){if(!this.config.adapter)throw new $("No socket adapter configured",0,"CONNECT","");return this.config.adapter}}function P(z){let B=z.secure??!1;return{host:z.host,port:z.port??(B?465:587),secure:B,...z.auth!==void 0?{auth:z.auth}:{},...z.requireTLS!==void 0?{requireTLS:z.requireTLS}:{},...z.dkim!==void 0?{dkim:z.dkim}:{},...z.tls!==void 0?{tls:z.tls}:{},...z.connectionTimeout!==void 0?{connectionTimeout:z.connectionTimeout}:{},...z.greetingTimeout!==void 0?{greetingTimeout:z.greetingTimeout}:{},...z.socketTimeout!==void 0?{socketTimeout:z.socketTimeout}:{},...z.direct!==void 0?{direct:z.direct}:{},...z.adapter!==void 0?{adapter:z.adapter}:{}}}async function D(z,B){let G=await J(z);N(G,[220],"greeting");let F=await I(z,B.host),W=F.some((Q)=>Q.toUpperCase()==="STARTTLS");if(!B.secure&&!z.secure&&W){await Y(z,Z({type:"STARTTLS"}));let Q=await J(z);N(Q,[220],"STARTTLS"),await z.startTLS(B.tls),F=await I(z,B.host)}if(B.auth){let Q=B.requireTLS??!0,V=z.secure||B.secure;if(Q&&!V)throw new $("Refusing to authenticate over unencrypted connection. Set requireTLS: false to disable this check (not recommended).",0,"AUTH","");await l(z,B.auth,F)}}async function S(z,B){await _(z,{type:"MAIL_FROM",address:B.envelope.from});let G=await J(z);N(G,[250],"MAIL FROM");let F=[],W=[];for(let K of B.envelope.to)if(await Y(z,Z({type:"RCPT_TO",address:K})),(await J(z)).isSuccess)F.push(K);else W.push(K);await _(z,{type:"DATA"});let Q=await J(z);N(Q,[354],"DATA");let V;try{await Y(z,Z({type:"DATA_BODY",content:B.raw})),V=await J(z)}catch(K){if(await Y(z,Z({type:"DATA_BODY",content:B.raw})),V=await J(z),V.isError)throw K}return N(V,[250],"DATA end"),{messageId:B.messageId,accepted:F,rejected:W,response:V.message,envelope:B.envelope}}async function H(z){try{await _(z,{type:"QUIT"}),await J(z)}catch{}finally{await z.close()}}async function I(z,B){await _(z,{type:"EHLO",domain:B});let G=await J(z);return N(G,[250],"EHLO"),v(G)}async function l(z,B,G){if(B.type==="OAUTH2"&&B.oauth2){let K=await new U(B.oauth2).buildXOAUTH2();await _(z,{type:"AUTH_XOAUTH2",xoauth2String:K});let x=await J(z);if(x.code===334)await Y(z,E("")),x=await J(z);N(x,[235],"AUTH XOAUTH2");return}let F=B.type??L(G);if(F==="CRAM-MD5"){let V=y(B,"CRAM-MD5");await _(z,{type:"AUTH_CRAM_MD5_INIT"});let K=await J(z);N(K,[334],"AUTH CRAM-MD5");let x=K.message.trim(),w=await k(x,B.user,V);await _(z,{type:"AUTH_CRAM_MD5_RESPONSE",response:w}),K=await J(z),N(K,[235],"AUTH CRAM-MD5 response");return}if(F==="PLAIN"){let V=y(B,"PLAIN");await Y(z,Z({type:"AUTH_PLAIN",user:B.user,pass:V}));let K=await J(z);N(K,[235],"AUTH PLAIN");return}let W=y(B,"LOGIN");await Y(z,Z({type:"AUTH_LOGIN",user:B.user,pass:W}));let Q=await J(z);N(Q,[334],"AUTH LOGIN"),await Y(z,j(B.user)),Q=await J(z),N(Q,[334],"AUTH LOGIN user"),await Y(z,A(W)),Q=await J(z),N(Q,[235],"AUTH LOGIN pass")}function y(z,B){if(!z.pass)throw new $(`Password required for ${B} authentication`,0,`AUTH ${B}`,"");return z.pass}async function _(z,B){await Y(z,Z(B))}async function Y(z,B){await z.write(B)}async function J(z){let B=[];for await(let G of z.read()){B.push(G);let F=q(B);if(F)return O(F)}throw new $("Connection closed while reading SMTP response",0,"READ","")}async function M(z){let G=await(await import("node:dns/promises")).resolveMx(z);if(G.length===0)throw new $(`No MX records for ${z}`,0,"MX","");return G.sort((F,W)=>F.priority-W.priority),G[0]?.exchange??z}export{T as k,P as l,D as m,S as n,H as o,J as p};
2
-
3
- //# debugId=27963FE5C2BAA8F464756E2164756E21
@@ -1,4 +0,0 @@
1
- import{A as J}from"./chunk-9zkv5azm.js";import{X as Q}from"./chunk-6bkj4y4r.js";class K extends Error{attempts;constructor(w){super(`All ${w.length} transports failed`);this.name="FallbackError",this.attempts=w}}var X=J;function T(w){if(w instanceof Q&&w.sentlyCode==="SMTP_AUTH_FAILED")return!1;let q=w?.statusCode;if(typeof q==="number"){if(q===400||q===401||q===403)return!1;return!0}if(w?.code===535)return!1;return!0}class U{transports;provider="fallback";shouldFallback;onFallback;cooldownMs;now;unhealthyUntil;mailerOnFallback;constructor(w,q){this.transports=w;if(w.length===0)throw Error("FallbackTransport requires at least one transport");this.shouldFallback=q?.shouldFallback??T,this.onFallback=q?.onFallback,this.cooldownMs=q?.cooldownMs??0,this.now=q?.now??(()=>Date.now()),this.unhealthyUntil=q?.cooldownState??new Map}setMailerOnFallback(w){this.mailerOnFallback=w}isCoolingDown(w){let q=this.unhealthyUntil.get(w);if(q===void 0)return!1;if(this.now()>=q)return this.unhealthyUntil.delete(w),!1;return!0}markUnhealthy(w){if(this.cooldownMs>0)this.unhealthyUntil.set(w,this.now()+this.cooldownMs)}async send(w){let q=[],z=[];for(let G=0;G<this.transports.length;G++){let M=this.transports[G],D=J(M);if(this.isCoolingDown(D)){z.push({provider:D,error:Error(`Provider ${D} is in cooldown`)});continue}try{return{...await M.send(w),provider:D,providerIndex:G}}catch(H){q.push({provider:D,error:H}),this.markUnhealthy(D);let N=this.findNextTryIndex(G);if(N===-1)break;if(this.shouldFallback(H)===!1)throw H;let R=J(this.transports[N]);if(this.onFallback?.(G,H),this.mailerOnFallback!==void 0)await this.mailerOnFallback(D,R,H)}}let B=[...q,...z];if(B.length===0)throw new K([{provider:"fallback",error:Error("All providers are in cooldown")}]);throw new K(B)}findNextTryIndex(w){for(let q=w+1;q<this.transports.length;q++){let z=J(this.transports[q]);if(!this.isCoolingDown(z))return q}return-1}async verify(){let q=(await this.verifyAll()).providers.find((z)=>z.ok);if(q!==void 0)return{ok:!0,provider:q.provider,...q.message!==void 0?{message:q.message}:{}};return{ok:!1,provider:"fallback",message:"no transport verified"}}async verifyAll(){let w=[];for(let q of this.transports){let z=J(q);if(q.verify===void 0){w.push({provider:z,ok:!1,message:"no verify() method"});continue}try{let B=await q.verify();w.push({provider:z,ok:B.ok,...B.message!==void 0?{message:B.message}:{}})}catch(B){w.push({provider:z,ok:!1,message:B instanceof Error?B.message:String(B)})}}return{ok:w.some((q)=>q.ok),providers:w}}async close(){await Promise.allSettled(this.transports.map((w)=>w.close?.()))}}
2
- export{K as h,X as i,U as j};
3
-
4
- //# debugId=3C85F02B08FD5F1E64756E2164756E21
@@ -1,4 +0,0 @@
1
- import{V}from"./chunk-5scdgffb.js";function W(q){for(let E=0;E<q.length;E++){let z=q.charCodeAt(E);if(z<=31||z===127||z===8232||z===8233)return z}return-1}function K(q,E="address"){let z=W(q);if(z!==-1){let G=z.toString(16).padStart(2,"0");throw Error(`Email ${E} contains a forbidden control character (0x${G}). CR, LF, NUL, and other control characters are not allowed.`)}}function X(q){if(Array.isArray(q))return q.flatMap((z)=>X(z));if(typeof q==="object"){if(K(q.address,"address"),q.name!==void 0)K(q.name,"display name");return[{...q}]}K(q,"address");let E=q.trim();if(!E)return[];return Y(E).map(Z)}function $(q){if(K(q.address,"address"),q.name)return K(q.name,"display name"),`${V(q.name)} <${q.address}>`;return q.address}function w(q){return X(q).map((E)=>E.address)}function x(q){if(W(q)!==-1)return!1;return/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(q)}function Y(q){let E=[],z="",G=!1,J=!1;for(let O=0;O<q.length;O++){let I=q[O]??"";if(I==='"'&&q[O-1]!=="\\"){G=!G,z+=I;continue}if(I==="<"&&!G){J=!0,z+=I;continue}if(I===">"&&!G){J=!1,z+=I;continue}if(I===","&&!G&&!J){if(z.trim())E.push(z.trim());z="";continue}z+=I}if(z.trim())E.push(z.trim());return E}function Z(q){let E=q.trim(),z=E.match(/^(?:"([^"]*)"|([^<]*?))\s*<([^>]+)>$/);if(z){let G=(z[1]??z[2]??"").trim(),J=(z[3]??"").trim();if(G)return{name:G,address:J};return{address:J}}if(E.startsWith('"')&&E.endsWith('"'))return{address:E.slice(1,-1)};return{address:E}}
2
- export{K as C,X as D,$ as E,w as F,x as G};
3
-
4
- //# debugId=80DC74FA55E2FA5964756E2164756E21
@@ -1,4 +0,0 @@
1
- import{_ as C}from"./chunk-nbmq8ejv.js";async function K(D,y){let E=D??[],j=[];for(let b of E){if(b.content instanceof Uint8Array){j.push(b);continue}if(b.path){let z;try{z=await import("node:fs/promises")}catch{throw Error("attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead")}if(y?.basePath){let{resolve:A,sep:H}=await import("node:path"),k=A(b.path),w=A(y.basePath);if(!(k===w||k.startsWith(w+H)))throw Error(`[sently] Attachment path "${k}" escapes basePath "${w}". Use absolute paths within the allowed directory.`)}let F=await z.readFile(b.path),{path:I,...G}=b;j.push({...G,content:new Uint8Array(F)});continue}if(typeof b.content==="string"){j.push(b);continue}j.push(b)}return j}
2
- export{K as v};
3
-
4
- //# debugId=3442FCDDB36F8C9264756E2164756E21
@@ -1,4 +0,0 @@
1
- import{T as D,W as J}from"./chunk-5scdgffb.js";var Q="https://oauth2.googleapis.com/token",$="https://login.microsoftonline.com/common/oauth2/v2.0/token",V=30000;class W{config;cachedToken=null;expiresAt=0;refreshPromise=null;constructor(m){if(this.config=m,m.accessToken)this.cachedToken=m.accessToken,this.expiresAt=Date.now()+3600000}async getAccessToken(){if(this.config.getToken)return this.config.getToken();if(this.cachedToken&&Date.now()<this.expiresAt-V)return this.cachedToken;if(!this.refreshPromise)this.refreshPromise=this.refreshAccessToken().finally(()=>{this.refreshPromise=null});return this.refreshPromise}async refreshAccessToken(){if(this.config.getToken){let v=await this.config.getToken();return this.cachedToken=v,this.expiresAt=Date.now()+3600000,v}let m=this.config.tokenUrl??Q,z=new URLSearchParams({client_id:this.config.clientId,client_secret:this.config.clientSecret,refresh_token:this.config.refreshToken,grant_type:"refresh_token"}),q=await fetch(m,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:z.toString()});if(!q.ok){let v=await q.text();throw Error(`OAuth2 token refresh failed (${q.status}): ${v}`)}let C=await q.json();return this.cachedToken=C.access_token,this.expiresAt=Date.now()+C.expires_in*1000,C.access_token}async buildXOAUTH2(){let m=await this.getAccessToken(),z=`user=${this.config.user}\x01auth=Bearer ${m}\x01\x01`;return D(J(z)).replace(/\r\n/g,"")}}
2
- export{Q as q,$ as r,W as s};
3
-
4
- //# debugId=81D6302F3F754CF964756E2164756E21
@@ -1,4 +0,0 @@
1
- import{_ as q}from"./chunk-nbmq8ejv.js";function w(){if(typeof Bun<"u")return"bun";if(typeof Deno<"u")return"deno";if(typeof caches<"u"&&globalThis.navigator?.userAgent==="Cloudflare-Workers")return"cf-workers";if(typeof window<"u")return"browser";if(typeof process<"u"&&process.versions?.node)return"node";return"unknown"}async function x(k){let v=w();switch(v){case"node":{let{NodeAdapter:g}=await import("./adapters/node.js");return new g(k)}case"bun":{let{BunAdapter:g}=await import("./adapters/bun.js");return new g(k)}case"deno":{let{DenoAdapter:g}=await import("./adapters/deno.js");return new g(k)}case"cf-workers":{let{CloudflareAdapter:g}=await import("./adapters/cf.js");return new g(k)}default:throw Error(`No socket adapter available for runtime: ${v}`)}}
2
- export{w,x};
3
-
4
- //# debugId=5FF26FD321F29A4864756E2164756E21