sently 0.8.0 → 0.9.1
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/AGENTS.md +33 -0
- package/CHANGELOG.md +82 -41
- package/README.md +89 -960
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.js +1 -1
- package/dist/adapters/node.js +1 -1
- package/dist/auth/oauth2.js +2 -2
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/chunk-0gqy32pe.js +4 -0
- package/dist/{chunk-n5dan5bz.js.map → chunk-0gqy32pe.js.map} +2 -2
- package/dist/{chunk-z62q7kqr.js → chunk-0qxws3kj.js} +2 -2
- package/dist/{chunk-z62q7kqr.js.map → chunk-0qxws3kj.js.map} +1 -1
- package/dist/chunk-29z7fkzy.js +4 -0
- package/dist/{chunk-wsymfbkn.js.map → chunk-29z7fkzy.js.map} +2 -2
- package/dist/chunk-5915vbcj.js +4 -0
- package/dist/chunk-5915vbcj.js.map +10 -0
- package/dist/{chunk-nbmq8ejv.js → chunk-6npp3x3c.js} +2 -2
- package/dist/{chunk-nbmq8ejv.js.map → chunk-6npp3x3c.js.map} +1 -1
- package/dist/chunk-7gy2q9sh.js +4 -0
- package/dist/chunk-7gy2q9sh.js.map +10 -0
- package/dist/{chunk-zst8an2p.js → chunk-8dty8c4p.js} +2 -2
- package/dist/{chunk-zst8an2p.js.map → chunk-8dty8c4p.js.map} +2 -2
- package/dist/{chunk-f3dz43cy.js → chunk-8njyga62.js} +2 -2
- package/dist/{chunk-f3dz43cy.js.map → chunk-8njyga62.js.map} +1 -1
- package/dist/chunk-93vqxxj2.js +4 -0
- package/dist/{chunk-qh90yr6d.js.map → chunk-93vqxxj2.js.map} +2 -2
- package/dist/chunk-b602dhck.js +4 -0
- package/dist/{chunk-wbvhaqcp.js.map → chunk-b602dhck.js.map} +2 -2
- package/dist/{chunk-9zkv5azm.js → chunk-gp4fwrs8.js} +2 -2
- package/dist/{chunk-9zkv5azm.js.map → chunk-gp4fwrs8.js.map} +1 -1
- package/dist/chunk-n0qeyzqm.js +13 -0
- package/dist/{chunk-8phpsns1.js.map → chunk-n0qeyzqm.js.map} +2 -2
- package/dist/chunk-qtkd5bak.js +4 -0
- package/dist/chunk-qtkd5bak.js.map +11 -0
- package/dist/chunk-skqhj0rm.js +4 -0
- package/dist/{chunk-vhxzhx8b.js.map → chunk-skqhj0rm.js.map} +2 -2
- package/dist/{chunk-6bkj4y4r.js → chunk-tamww15j.js} +2 -2
- package/dist/{chunk-6bkj4y4r.js.map → chunk-tamww15j.js.map} +1 -1
- package/dist/chunk-vjpds2pf.js +3 -0
- package/dist/{chunk-m07smapm.js.map → chunk-vjpds2pf.js.map} +2 -2
- package/dist/chunk-yp311efr.js +4 -0
- package/dist/chunk-yp311efr.js.map +10 -0
- package/dist/chunk-ywq6s10g.js +5 -0
- package/dist/{chunk-5scdgffb.js.map → chunk-ywq6s10g.js.map} +3 -3
- package/dist/core/base64.d.ts +10 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/hooks.d.ts +14 -0
- package/dist/core/plugin.d.ts +7 -7
- package/dist/core/push-endpoint.d.ts +29 -0
- package/dist/core/push-types.d.ts +98 -0
- package/dist/core/sms-types.d.ts +76 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/whatsapp-types.d.ts +116 -0
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +1 -1
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +2 -2
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +5 -32
- package/dist/index.js +0 -35
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/observability/console.js +1 -1
- package/dist/plugins/react.js +2 -2
- package/dist/plugins/react.js.map +2 -2
- package/dist/plugins/template.js +1 -1
- package/dist/pool/pool.js +2 -2
- package/dist/pool/pool.js.map +1 -1
- package/dist/push.d.ts +26 -0
- package/dist/push.js +3 -0
- package/dist/push.js.map +10 -0
- package/dist/sms.d.ts +26 -0
- package/dist/sms.js +3 -0
- package/dist/sms.js.map +10 -0
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +2 -2
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/cloudflare-email.js +2 -2
- package/dist/transports/cloudflare-email.js.map +2 -2
- package/dist/transports/fallback.js +2 -2
- package/dist/transports/fallback.js.map +1 -1
- package/dist/transports/loops.js +2 -2
- package/dist/transports/loops.js.map +2 -2
- package/dist/transports/mailersend.js +2 -2
- package/dist/transports/mailersend.js.map +2 -2
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/mailtrap.js +2 -2
- package/dist/transports/mailtrap.js.map +2 -2
- package/dist/transports/msegat.d.ts +132 -0
- package/dist/transports/msegat.js +3 -0
- package/dist/transports/msegat.js.map +10 -0
- package/dist/transports/plunk.js +2 -2
- package/dist/transports/plunk.js.map +2 -2
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +2 -2
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/transports/sndr.d.ts +41 -0
- package/dist/transports/sndr.js +3 -0
- package/dist/transports/sndr.js.map +10 -0
- package/dist/transports/sparkpost.js +2 -2
- package/dist/transports/sparkpost.js.map +2 -2
- package/dist/transports/taqnyat-mail.d.ts +33 -0
- package/dist/transports/taqnyat-mail.js +3 -0
- package/dist/transports/taqnyat-mail.js.map +10 -0
- package/dist/transports/taqnyat-phone.d.ts +4 -0
- package/dist/transports/taqnyat-sms.d.ts +131 -0
- package/dist/transports/taqnyat-sms.js +3 -0
- package/dist/transports/taqnyat-sms.js.map +10 -0
- package/dist/transports/taqnyat-whatsapp.d.ts +51 -0
- package/dist/transports/taqnyat-whatsapp.js +3 -0
- package/dist/transports/taqnyat-whatsapp.js.map +10 -0
- package/dist/transports/twilio-sms.d.ts +66 -0
- package/dist/transports/twilio-sms.js +3 -0
- package/dist/transports/twilio-sms.js.map +10 -0
- package/dist/transports/webpush.d.ts +46 -0
- package/dist/transports/webpush.js +3 -0
- package/dist/transports/webpush.js.map +10 -0
- package/dist/transports/weighted-fallback.js +2 -2
- package/dist/transports/weighted-fallback.js.map +1 -1
- package/dist/transports/whatsapp-cloud.d.ts +56 -0
- package/dist/transports/whatsapp-cloud.js +3 -0
- package/dist/transports/whatsapp-cloud.js.map +10 -0
- package/dist/webhooks/brevo.js +1 -1
- package/dist/webhooks/mailgun.js +1 -1
- package/dist/webhooks/postmark.js +1 -1
- package/dist/webhooks/resend.js +1 -1
- package/dist/webhooks/sendgrid.js +1 -1
- package/dist/webhooks/ses.js +1 -1
- package/dist/webhooks/sndr.d.ts +16 -0
- package/dist/webhooks/sndr.js +3 -0
- package/dist/webhooks/sndr.js.map +10 -0
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/dist/webhooks.d.ts +9 -2
- package/dist/webhooks.js +4 -0
- package/dist/whatsapp.d.ts +26 -0
- package/dist/whatsapp.js +3 -0
- package/dist/whatsapp.js.map +10 -0
- package/package.json +85 -4
- package/site/README.md +26 -0
- package/site/content/docs/ai/index.mdx +13 -0
- package/site/content/docs/ai/llms-txt.mdx +12 -0
- package/site/content/docs/ai/mcp.mdx +16 -0
- package/site/content/docs/ai/meta.json +9 -0
- package/site/content/docs/channels/email.mdx +100 -0
- package/site/content/docs/channels/hooks.mdx +87 -0
- package/site/content/docs/channels/index.mdx +81 -0
- package/site/content/docs/channels/meta.json +12 -0
- package/site/content/docs/channels/push.mdx +92 -0
- package/site/content/docs/channels/sms.mdx +88 -0
- package/site/content/docs/channels/whatsapp.mdx +95 -0
- package/site/content/docs/decorators/fallback.mdx +34 -0
- package/site/content/docs/decorators/idempotency.mdx +34 -0
- package/site/content/docs/decorators/index.mdx +50 -0
- package/site/content/docs/decorators/meta.json +12 -0
- package/site/content/docs/decorators/preview.mdx +34 -0
- package/site/content/docs/decorators/retry.mdx +34 -0
- package/site/content/docs/decorators/weighted-fallback.mdx +34 -0
- package/site/content/docs/get-started/entrypoints.mdx +114 -0
- package/site/content/docs/get-started/index.mdx +14 -0
- package/site/content/docs/get-started/installation.mdx +78 -0
- package/site/content/docs/get-started/introduction.mdx +77 -0
- package/site/content/docs/get-started/meta.json +12 -0
- package/site/content/docs/get-started/migrate-nodemailer.mdx +23 -0
- package/site/content/docs/get-started/runtimes.mdx +17 -0
- package/site/content/docs/guides/adapters.mdx +26 -0
- package/site/content/docs/guides/attachments.mdx +19 -0
- package/site/content/docs/guides/bundle-size.mdx +78 -0
- package/site/content/docs/guides/dkim.mdx +26 -0
- package/site/content/docs/guides/index.mdx +15 -0
- package/site/content/docs/guides/meta.json +21 -0
- package/site/content/docs/guides/oauth2.mdx +23 -0
- package/site/content/docs/guides/observability.mdx +16 -0
- package/site/content/docs/guides/plugins-template.mdx +22 -0
- package/site/content/docs/guides/pool.mdx +24 -0
- package/site/content/docs/guides/react-email.mdx +20 -0
- package/site/content/docs/guides/security.mdx +17 -0
- package/site/content/docs/guides/send-bulk.mdx +18 -0
- package/site/content/docs/guides/vendor-extras-otp.mdx +18 -0
- package/site/content/docs/guides/webhooks.mdx +120 -0
- package/site/content/docs/guides/webpush-interop.mdx +18 -0
- package/site/content/docs/index.mdx +15 -0
- package/site/content/docs/meta.json +15 -0
- package/site/content/docs/quick-start/email.mdx +40 -0
- package/site/content/docs/quick-start/index.mdx +15 -0
- package/site/content/docs/quick-start/meta.json +5 -0
- package/site/content/docs/quick-start/push.mdx +36 -0
- package/site/content/docs/quick-start/sms.mdx +36 -0
- package/site/content/docs/quick-start/whatsapp.mdx +38 -0
- package/site/content/docs/reference/errors.mdx +22 -0
- package/site/content/docs/reference/exports.mdx +83 -0
- package/site/content/docs/reference/index.mdx +15 -0
- package/site/content/docs/reference/mail-options.mdx +22 -0
- package/site/content/docs/reference/meta.json +15 -0
- package/site/content/docs/reference/push-options.mdx +20 -0
- package/site/content/docs/reference/sms-options.mdx +17 -0
- package/site/content/docs/reference/transport-contracts.mdx +17 -0
- package/site/content/docs/reference/webhook-events.mdx +70 -0
- package/site/content/docs/reference/whatsapp-options.mdx +15 -0
- package/site/content/docs/transports/brevo.mdx +40 -0
- package/site/content/docs/transports/cloudflare-email.mdx +40 -0
- package/site/content/docs/transports/index.mdx +105 -0
- package/site/content/docs/transports/loops.mdx +41 -0
- package/site/content/docs/transports/mailersend.mdx +40 -0
- package/site/content/docs/transports/mailgun.mdx +42 -0
- package/site/content/docs/transports/mailtrap.mdx +42 -0
- package/site/content/docs/transports/meta.json +32 -0
- package/site/content/docs/transports/msegat.mdx +42 -0
- package/site/content/docs/transports/plunk.mdx +40 -0
- package/site/content/docs/transports/postmark.mdx +40 -0
- package/site/content/docs/transports/resend.mdx +43 -0
- package/site/content/docs/transports/sendgrid.mdx +40 -0
- package/site/content/docs/transports/ses.mdx +44 -0
- package/site/content/docs/transports/smtp.mdx +47 -0
- package/site/content/docs/transports/sndr.mdx +144 -0
- package/site/content/docs/transports/sparkpost.mdx +41 -0
- package/site/content/docs/transports/taqnyat-mail.mdx +41 -0
- package/site/content/docs/transports/taqnyat-sms.mdx +41 -0
- package/site/content/docs/transports/taqnyat-whatsapp.mdx +40 -0
- package/site/content/docs/transports/twilio-sms.mdx +128 -0
- package/site/content/docs/transports/webpush.mdx +43 -0
- package/site/content/docs/transports/whatsapp-cloud.mdx +42 -0
- package/dist/chunk-5scdgffb.js +0 -5
- package/dist/chunk-8phpsns1.js +0 -13
- package/dist/chunk-m07smapm.js +0 -3
- package/dist/chunk-n5dan5bz.js +0 -4
- package/dist/chunk-qh90yr6d.js +0 -4
- package/dist/chunk-vhxzhx8b.js +0 -4
- package/dist/chunk-wbvhaqcp.js +0 -4
- package/dist/chunk-wsymfbkn.js +0 -4
- package/dist/chunk-yr56b0ts.js +0 -4
- package/dist/chunk-yr56b0ts.js.map +0 -11
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Channels
|
|
3
|
+
description: Send through a stable channel API while transports handle providers.
|
|
4
|
+
icon: FolderOpen
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Choose a channel sender for the kind of message your application sends.
|
|
9
|
+
Each sender accepts a provider transport, so delivery providers can change without rewriting send calls.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">Application code calls a sently channel sender; provider-specific code stays in its transport.</Callout>
|
|
12
|
+
|
|
13
|
+
<Steps>
|
|
14
|
+
<Step title="Choose the message channel">
|
|
15
|
+
|
|
16
|
+
| Channel | Sender | Send shape |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Email | `createMailer` or `createSMTPMailer` | Sender, recipient, subject, and body. |
|
|
19
|
+
| SMS | `createSmsSender` | Recipient number and body. |
|
|
20
|
+
| WhatsApp | `createWhatsAppSender` | Template or text message. |
|
|
21
|
+
| Push | `createPushSender` | Browser subscription, title, and body. |
|
|
22
|
+
|
|
23
|
+
</Step>
|
|
24
|
+
<Step title="Wire in a transport">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createSmsSender } from "sently/sms";
|
|
28
|
+
import { TwilioSmsTransport } from "sently/transports/twilio-sms";
|
|
29
|
+
|
|
30
|
+
const sms = createSmsSender({
|
|
31
|
+
transport: new TwilioSmsTransport({
|
|
32
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID!,
|
|
33
|
+
authToken: process.env.TWILIO_AUTH_TOKEN!,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await sms.send({
|
|
38
|
+
to: "+15551234567",
|
|
39
|
+
body: "Your order is on its way.",
|
|
40
|
+
from: "+15557654321",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
</Step>
|
|
45
|
+
</Steps>
|
|
46
|
+
|
|
47
|
+
## Channel factories
|
|
48
|
+
|
|
49
|
+
| Factory | Async | When to use it |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `createMailer` | Yes | Email with an explicit transport. |
|
|
52
|
+
| `createSMTPMailer` | Yes | Email configured with SMTP host, port, and authentication. |
|
|
53
|
+
| `createSmsSender` | No | SMS with an `SmsTransport`. |
|
|
54
|
+
| `createWhatsAppSender` | No | WhatsApp with a `WhatsAppTransport`. |
|
|
55
|
+
| `createPushSender` | No | Web Push with a `PushTransport`. |
|
|
56
|
+
|
|
57
|
+
## Troubleshooting
|
|
58
|
+
|
|
59
|
+
<Accordions>
|
|
60
|
+
<Accordion title="Should I call a provider SDK directly?">
|
|
61
|
+
Use the matching sently sender for normal delivery. Provider-only features belong on the concrete transport rather than the shared channel contract.
|
|
62
|
+
</Accordion>
|
|
63
|
+
<Accordion title="How do I switch providers?">
|
|
64
|
+
Keep the sender call and create it with a different transport for the same channel.
|
|
65
|
+
</Accordion>
|
|
66
|
+
</Accordions>
|
|
67
|
+
|
|
68
|
+
## Learn more
|
|
69
|
+
|
|
70
|
+
- [Choose an entrypoint](../get-started/entrypoints)
|
|
71
|
+
- [Browse transports](../transports)
|
|
72
|
+
|
|
73
|
+
## Next
|
|
74
|
+
|
|
75
|
+
<Cards>
|
|
76
|
+
<Card title="Email" href="/docs/channels/email" />
|
|
77
|
+
<Card title="SMS" href="/docs/channels/sms" />
|
|
78
|
+
<Card title="WhatsApp" href="/docs/channels/whatsapp" />
|
|
79
|
+
<Card title="Push" href="/docs/channels/push" />
|
|
80
|
+
<Card title="Hooks" href="/docs/channels/hooks" />
|
|
81
|
+
</Cards>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Push
|
|
3
|
+
description: Send encrypted Web Push notifications through a push transport.
|
|
4
|
+
icon: Bell
|
|
5
|
+
source: "src/push.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Use Web Push for browser notifications after a browser has granted permission and returned a subscription.
|
|
9
|
+
The sender passes the subscription and notification payload to a push transport.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">Treat subscription endpoints as secrets: they are long-lived delivery tokens.</Callout>
|
|
12
|
+
|
|
13
|
+
<Steps>
|
|
14
|
+
<Step title="Create a Push sender">
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createPushSender } from "sently/push";
|
|
18
|
+
import { WebPushTransport } from "sently/transports/webpush";
|
|
19
|
+
|
|
20
|
+
const push = createPushSender({
|
|
21
|
+
transport: new WebPushTransport({
|
|
22
|
+
vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,
|
|
23
|
+
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,
|
|
24
|
+
subject: "mailto:you@example.com",
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
</Step>
|
|
30
|
+
<Step title="Send to a stored browser subscription">
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const result = await push.send({
|
|
34
|
+
subscription: {
|
|
35
|
+
endpoint: "https://fcm.googleapis.com/fcm/send/example",
|
|
36
|
+
keys: { p256dh: "browser-public-key", auth: "browser-auth-secret" },
|
|
37
|
+
},
|
|
38
|
+
title: "Report ready",
|
|
39
|
+
body: "Your weekly report is ready to view.",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(result.status);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
</Step>
|
|
46
|
+
</Steps>
|
|
47
|
+
|
|
48
|
+
## Sender configuration
|
|
49
|
+
|
|
50
|
+
| Option | Type | Meaning |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `transport` | `PushTransport` | Required delivery implementation. |
|
|
53
|
+
| `plugins` | `PushPlugin[]` | Optional transforms run before delivery. |
|
|
54
|
+
| `hooks` | `PushHooks` | Optional callbacks for delivery activity. |
|
|
55
|
+
|
|
56
|
+
## Push and VAPID options
|
|
57
|
+
|
|
58
|
+
| Option | Type | Meaning |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `subscription` | `PushSubscription` | Required browser endpoint plus `p256dh` and `auth` keys. |
|
|
61
|
+
| `title`, `body` | `string` | Required notification content. |
|
|
62
|
+
| `data`, `icon` | `Record<string, unknown>`, `string` | Optional application payload and icon URL. |
|
|
63
|
+
| `ttl` | `number` | Optional delivery lifetime in seconds. |
|
|
64
|
+
| `subject` | `string` | Required Web Push contact URI for the VAPID transport. |
|
|
65
|
+
|
|
66
|
+
## Troubleshooting
|
|
67
|
+
|
|
68
|
+
<Accordions>
|
|
69
|
+
<Accordion title="Why does the transport reject my endpoint?">
|
|
70
|
+
Web Push validates subscription endpoint hosts before sending. Add only exact private relay hostnames with `allowedEndpointHosts` when needed.
|
|
71
|
+
</Accordion>
|
|
72
|
+
<Accordion title="Why is the endpoint redacted in hooks?">
|
|
73
|
+
The endpoint path can contain a long-lived delivery token, so hook context keeps only a redacted form.
|
|
74
|
+
</Accordion>
|
|
75
|
+
<Accordion title="What does verify do?">
|
|
76
|
+
`push.verify()` delegates to the transport when available; the Web Push transport checks its VAPID credential shape.
|
|
77
|
+
</Accordion>
|
|
78
|
+
</Accordions>
|
|
79
|
+
|
|
80
|
+
## Learn more
|
|
81
|
+
|
|
82
|
+
- [Push options](../reference/push-options)
|
|
83
|
+
- [Transport contracts](../reference/transport-contracts)
|
|
84
|
+
- [Web Push interoperability](../guides/webpush-interop)
|
|
85
|
+
- [Hooks](./hooks)
|
|
86
|
+
|
|
87
|
+
## Next
|
|
88
|
+
|
|
89
|
+
<Cards>
|
|
90
|
+
<Card title="Web Push transport" href="/docs/transports/webpush" />
|
|
91
|
+
<Card title="All transports" href="/docs/transports" />
|
|
92
|
+
</Cards>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SMS
|
|
3
|
+
description: Send SMS through a sender and an SMS transport.
|
|
4
|
+
icon: MessageSquare
|
|
5
|
+
source: "src/sms.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Use SMS for short transactional messages such as codes, delivery updates, and alerts.
|
|
9
|
+
The sender stays the same when you change the provider transport.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">Put the recipient, body, and optional `from` value on `sms.send`, not on the shared sender.</Callout>
|
|
12
|
+
|
|
13
|
+
<Steps>
|
|
14
|
+
<Step title="Create an SMS sender">
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createSmsSender } from "sently/sms";
|
|
18
|
+
import { TwilioSmsTransport } from "sently/transports/twilio-sms";
|
|
19
|
+
|
|
20
|
+
const sms = createSmsSender({
|
|
21
|
+
transport: new TwilioSmsTransport({
|
|
22
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID!,
|
|
23
|
+
authToken: process.env.TWILIO_AUTH_TOKEN!,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
</Step>
|
|
29
|
+
<Step title="Send a message">
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const result = await sms.send({
|
|
33
|
+
to: "+15551234567",
|
|
34
|
+
body: "Your verification code is 482915.",
|
|
35
|
+
from: "+15557654321",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log(result.status);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
</Step>
|
|
42
|
+
</Steps>
|
|
43
|
+
|
|
44
|
+
## Sender configuration
|
|
45
|
+
|
|
46
|
+
| Option | Type | Meaning |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `transport` | `SmsTransport` | Required provider delivery implementation. |
|
|
49
|
+
| `plugins` | `SmsPlugin[]` | Optional transforms run before delivery. |
|
|
50
|
+
| `hooks` | `SmsHooks` | Optional callbacks for delivery activity. |
|
|
51
|
+
|
|
52
|
+
## Message options
|
|
53
|
+
|
|
54
|
+
| Option | Type | Meaning |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `to` | `string` | Required recipient number; E.164 is recommended. |
|
|
57
|
+
| `body` | `string` | Required message text. |
|
|
58
|
+
| `from` | `string` | Optional sender number or alphanumeric sender ID. |
|
|
59
|
+
| `messageId` | `string` | Optional client-supplied identifier. |
|
|
60
|
+
|
|
61
|
+
Twilio also accepts a default `from` or `messagingServiceSid` when you construct its concrete transport.
|
|
62
|
+
|
|
63
|
+
## Troubleshooting
|
|
64
|
+
|
|
65
|
+
<Accordions>
|
|
66
|
+
<Accordion title="Why does Twilio reject my send without a sender?">
|
|
67
|
+
Twilio requires `from` on the message or its transport configuration, unless its transport has `messagingServiceSid`.
|
|
68
|
+
</Accordion>
|
|
69
|
+
<Accordion title="Can I change SMS providers?">
|
|
70
|
+
Yes. Construct the sender with another `SmsTransport`; calls to `sms.send` keep the same message shape.
|
|
71
|
+
</Accordion>
|
|
72
|
+
<Accordion title="What does verify do?">
|
|
73
|
+
`sms.verify()` delegates to the transport when available; otherwise it reports a successful sender result.
|
|
74
|
+
</Accordion>
|
|
75
|
+
</Accordions>
|
|
76
|
+
|
|
77
|
+
## Learn more
|
|
78
|
+
|
|
79
|
+
- [SMS options](../reference/sms-options)
|
|
80
|
+
- [Transport contracts](../reference/transport-contracts)
|
|
81
|
+
- [Hooks](./hooks)
|
|
82
|
+
|
|
83
|
+
## Next
|
|
84
|
+
|
|
85
|
+
<Cards>
|
|
86
|
+
<Card title="SMS transports" href="/docs/transports" />
|
|
87
|
+
<Card title="Provider OTP extras" href="/docs/guides/vendor-extras-otp" />
|
|
88
|
+
</Cards>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WhatsApp
|
|
3
|
+
description: Send template or session text messages through a WhatsApp transport.
|
|
4
|
+
icon: MessagesSquare
|
|
5
|
+
source: "src/whatsapp.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Use WhatsApp for customer messages sent through a WhatsApp provider transport.
|
|
9
|
+
Send a template for business-initiated delivery, or text during an open customer-service window.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">Use templates for business-initiated messages outside the 24-hour customer-service window.</Callout>
|
|
12
|
+
|
|
13
|
+
<Steps>
|
|
14
|
+
<Step title="Create a WhatsApp sender">
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createWhatsAppSender } from "sently/whatsapp";
|
|
18
|
+
import { WhatsAppCloudTransport } from "sently/transports/whatsapp-cloud";
|
|
19
|
+
|
|
20
|
+
const whatsapp = createWhatsAppSender({
|
|
21
|
+
transport: new WhatsAppCloudTransport({
|
|
22
|
+
accessToken: process.env.WHATSAPP_TOKEN!,
|
|
23
|
+
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
</Step>
|
|
29
|
+
<Step title="Send an approved template">
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const result = await whatsapp.send({
|
|
33
|
+
to: "15551234567",
|
|
34
|
+
template: {
|
|
35
|
+
name: "welcome",
|
|
36
|
+
language: "en_US",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(result.messageId);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</Step>
|
|
44
|
+
</Steps>
|
|
45
|
+
|
|
46
|
+
## Sender configuration
|
|
47
|
+
|
|
48
|
+
| Option | Type | Meaning |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `transport` | `WhatsAppTransport` | Required provider delivery implementation. |
|
|
51
|
+
| `plugins` | `WhatsAppPlugin[]` | Optional transforms run before delivery. |
|
|
52
|
+
| `hooks` | `WhatsAppHooks` | Optional callbacks for delivery activity. |
|
|
53
|
+
|
|
54
|
+
## Message options
|
|
55
|
+
|
|
56
|
+
| Shape | Required fields | Use |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Template | `to`, `template.name`, `template.language` | Business-initiated delivery. |
|
|
59
|
+
| Text | `to`, `text` | An open 24-hour customer-service window. |
|
|
60
|
+
|
|
61
|
+
Template messages may include `template.components` for header, body, or button parameters.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await whatsapp.send({
|
|
65
|
+
to: "15551234567",
|
|
66
|
+
text: "Thanks—we received your reply.",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Troubleshooting
|
|
71
|
+
|
|
72
|
+
<Accordions>
|
|
73
|
+
<Accordion title="Why did my free-text message fail?">
|
|
74
|
+
Free text is valid only during an open customer-service window. Use a pre-approved template outside that window.
|
|
75
|
+
</Accordion>
|
|
76
|
+
<Accordion title="Can I change WhatsApp providers?">
|
|
77
|
+
Yes. Construct the sender with another `WhatsAppTransport`; message shapes remain template or text.
|
|
78
|
+
</Accordion>
|
|
79
|
+
<Accordion title="What does verify do?">
|
|
80
|
+
`whatsapp.verify()` delegates to the transport when available; otherwise it reports a successful sender result.
|
|
81
|
+
</Accordion>
|
|
82
|
+
</Accordions>
|
|
83
|
+
|
|
84
|
+
## Learn more
|
|
85
|
+
|
|
86
|
+
- [WhatsApp options](../reference/whatsapp-options)
|
|
87
|
+
- [Transport contracts](../reference/transport-contracts)
|
|
88
|
+
- [Hooks](./hooks)
|
|
89
|
+
|
|
90
|
+
## Next
|
|
91
|
+
|
|
92
|
+
<Cards>
|
|
93
|
+
<Card title="WhatsApp Cloud transport" href="/docs/transports/whatsapp-cloud" />
|
|
94
|
+
<Card title="All transports" href="/docs/transports" />
|
|
95
|
+
</Cards>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fallback
|
|
3
|
+
description: Fail over across ordered email transports.
|
|
4
|
+
icon: GitBranch
|
|
5
|
+
source: "src/transports/fallback.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Fail over across ordered email transports.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">Permanent client errors do not fall through by default.</Callout>
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Pass a non-empty transport array; options include `shouldFallback`, `onFallback`, and `cooldownMs`.
|
|
15
|
+
|
|
16
|
+
<Steps><Step title="Wrap an email transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { FallbackTransport } from "sently/transports/fallback";
|
|
20
|
+
|
|
21
|
+
const transport = new FallbackTransport([primary, secondary], { cooldownMs: 300_000 });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step><Step title="Create the mailer">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const mailer = await createMailer({ transport });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step></Steps>
|
|
31
|
+
|
|
32
|
+
<Accordions><Accordion title="Does this replace the inner provider?">No. It delegates sends to the wrapped transport.</Accordion></Accordions>
|
|
33
|
+
|
|
34
|
+
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Idempotency
|
|
3
|
+
description: Deduplicate replayed email sends with an idempotency key.
|
|
4
|
+
icon: KeyRound
|
|
5
|
+
source: "src/idempotency.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Deduplicate replayed email sends with an idempotency key.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">Wrap it outside RetryTransport so all retry attempts share a key.</Callout>
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
The optional options are `store` and `ttlMs`; memory is the default store.
|
|
15
|
+
|
|
16
|
+
<Steps><Step title="Wrap an email transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { IdempotencyTransport } from "sently/idempotency";
|
|
20
|
+
|
|
21
|
+
const transport = new IdempotencyTransport(inner, { ttlMs: 86_400_000 });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step><Step title="Create the mailer">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const mailer = await createMailer({ transport });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step></Steps>
|
|
31
|
+
|
|
32
|
+
<Accordions><Accordion title="Does this replace the inner provider?">No. It delegates sends to the wrapped transport.</Accordion></Accordions>
|
|
33
|
+
|
|
34
|
+
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Decorators
|
|
3
|
+
description: Wrap an email transport with retry, fallback, preview, or idempotency.
|
|
4
|
+
icon: Layers
|
|
5
|
+
source: "src/transports/retry.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Decorators wrap another email transport. The mailer still calls `send` the same way — only the transport path changes.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">Pass the outermost decorator to `createMailer`. Inner transports stay providers (or other decorators).</Callout>
|
|
11
|
+
|
|
12
|
+
## Available decorators
|
|
13
|
+
|
|
14
|
+
| Decorator | Import |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| [Preview](./preview) | `sently/transports/preview` |
|
|
17
|
+
| [Retry](./retry) | `sently/transports/retry` |
|
|
18
|
+
| [Fallback](./fallback) | `sently/transports/fallback` |
|
|
19
|
+
| [Weighted fallback](./weighted-fallback) | `sently/transports/weighted-fallback` |
|
|
20
|
+
| [Idempotency](./idempotency) | `sently/idempotency` |
|
|
21
|
+
|
|
22
|
+
## Typical stack
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createMailer } from "sently/mailer";
|
|
26
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
27
|
+
import { RetryTransport } from "sently/transports/retry";
|
|
28
|
+
import { FallbackTransport } from "sently/transports/fallback";
|
|
29
|
+
|
|
30
|
+
const transport = new FallbackTransport([
|
|
31
|
+
new RetryTransport(new ResendTransport({ apiKey: process.env.RESEND_API_KEY! })),
|
|
32
|
+
new ResendTransport({ apiKey: process.env.RESEND_BACKUP_KEY! }),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const mailer = await createMailer({ transport });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Learn more
|
|
39
|
+
|
|
40
|
+
- [Transports](../transports)
|
|
41
|
+
- [Email channel](../channels/email)
|
|
42
|
+
- [Hooks](../channels/hooks)
|
|
43
|
+
|
|
44
|
+
## Next
|
|
45
|
+
|
|
46
|
+
<Cards>
|
|
47
|
+
<Card title="Retry" href="/docs/decorators/retry" />
|
|
48
|
+
<Card title="Fallback" href="/docs/decorators/fallback" />
|
|
49
|
+
<Card title="Transports" href="/docs/transports" />
|
|
50
|
+
</Cards>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Preview
|
|
3
|
+
description: Write email previews to disk instead of delivering them.
|
|
4
|
+
icon: Eye
|
|
5
|
+
source: "src/transports/preview.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Write email previews to disk instead of delivering them.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">Preview is for development, not delivery.</Callout>
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
`outDir`, `open`, and `format` are optional; defaults are `./.emails`, false, and `eml`.
|
|
15
|
+
|
|
16
|
+
<Steps><Step title="Wrap an email transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { PreviewTransport } from "sently/transports/preview";
|
|
20
|
+
|
|
21
|
+
const transport = new PreviewTransport({ outDir: ".emails", open: true });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step><Step title="Create the mailer">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const mailer = await createMailer({ transport });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step></Steps>
|
|
31
|
+
|
|
32
|
+
<Accordions><Accordion title="Does this replace the inner provider?">No. It delegates sends to the wrapped transport.</Accordion></Accordions>
|
|
33
|
+
|
|
34
|
+
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Retry
|
|
3
|
+
description: Retry failed email sends with configurable backoff.
|
|
4
|
+
icon: RotateCw
|
|
5
|
+
source: "src/transports/retry.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Retry failed email sends with configurable backoff.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">Authentication errors are not retried.</Callout>
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
`maxAttempts`, `backoff`, `baseDelay`, `retryOn`, and `onRetry` are optional.
|
|
15
|
+
|
|
16
|
+
<Steps><Step title="Wrap an email transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { RetryTransport } from "sently/transports/retry";
|
|
20
|
+
|
|
21
|
+
const transport = new RetryTransport(new ResendTransport({ apiKey: "..." }), { maxAttempts: 3 });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step><Step title="Create the mailer">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const mailer = await createMailer({ transport });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step></Steps>
|
|
31
|
+
|
|
32
|
+
<Accordions><Accordion title="Does this replace the inner provider?">No. It delegates sends to the wrapped transport.</Accordion></Accordions>
|
|
33
|
+
|
|
34
|
+
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Weighted fallback
|
|
3
|
+
description: Choose a weighted primary transport and fail over on errors.
|
|
4
|
+
icon: Scale
|
|
5
|
+
source: "src/transports/weighted-fallback.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Choose a weighted primary transport and fail over on errors.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">At least one entry with positive total weight is required.</Callout>
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Each entry has `transport` and positive `weight`; it accepts fallback options and optional `random`.
|
|
15
|
+
|
|
16
|
+
<Steps><Step title="Wrap an email transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { WeightedFallbackTransport } from "sently/transports/weighted-fallback";
|
|
20
|
+
|
|
21
|
+
const transport = new WeightedFallbackTransport([{ transport: primary, weight: 80 }, { transport: secondary, weight: 20 }]);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step><Step title="Create the mailer">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const mailer = await createMailer({ transport });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</Step></Steps>
|
|
31
|
+
|
|
32
|
+
<Accordions><Accordion title="Does this replace the inner provider?">No. It delegates sends to the wrapped transport.</Accordion></Accordions>
|
|
33
|
+
|
|
34
|
+
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|