sently 0.9.1 → 0.10.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +60 -0
- package/README.md +18 -16
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +1 -1
- 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/channel-result.d.ts +6 -0
- package/dist/channel-result.js +1 -0
- package/dist/{chunk-skqhj0rm.js → chunk-1ke3nmyg.js} +3 -3
- package/dist/{chunk-skqhj0rm.js.map → chunk-1ke3nmyg.js.map} +1 -1
- package/dist/{chunk-7gy2q9sh.js → chunk-5es0v1j7.js} +2 -2
- package/dist/{chunk-7gy2q9sh.js.map → chunk-5es0v1j7.js.map} +1 -1
- package/dist/{chunk-0qxws3kj.js → chunk-7wtfjx0d.js} +2 -2
- package/dist/{chunk-0qxws3kj.js.map → chunk-7wtfjx0d.js.map} +1 -1
- package/dist/chunk-8kpgbrba.js +4 -0
- package/dist/{chunk-93vqxxj2.js.map → chunk-8kpgbrba.js.map} +2 -2
- package/dist/{chunk-vjpds2pf.js → chunk-a057jzsy.js} +2 -2
- package/dist/{chunk-vjpds2pf.js.map → chunk-a057jzsy.js.map} +1 -1
- package/dist/{chunk-8njyga62.js → chunk-bhr7w3gg.js} +2 -2
- package/dist/{chunk-8njyga62.js.map → chunk-bhr7w3gg.js.map} +1 -1
- package/dist/chunk-cs1tvehn.js +4 -0
- package/dist/chunk-cs1tvehn.js.map +10 -0
- package/dist/{chunk-29z7fkzy.js → chunk-d5aw5vag.js} +3 -3
- package/dist/{chunk-29z7fkzy.js.map → chunk-d5aw5vag.js.map} +1 -1
- package/dist/chunk-f5x8vesr.js +4 -0
- package/dist/{chunk-b602dhck.js.map → chunk-f5x8vesr.js.map} +2 -2
- package/dist/chunk-fv0bemgf.js +4 -0
- package/dist/{chunk-yp311efr.js.map → chunk-fv0bemgf.js.map} +3 -3
- package/dist/{chunk-ywq6s10g.js → chunk-hnzmn4s4.js} +2 -2
- package/dist/{chunk-ywq6s10g.js.map → chunk-hnzmn4s4.js.map} +1 -1
- package/dist/chunk-htdg075t.js +4 -0
- package/dist/{chunk-qtkd5bak.js.map → chunk-htdg075t.js.map} +4 -3
- package/dist/{chunk-0gqy32pe.js → chunk-q2mahnb7.js} +3 -3
- package/dist/chunk-q2mahnb7.js.map +10 -0
- package/dist/chunk-sbgt4165.js +4 -0
- package/dist/chunk-sbgt4165.js.map +10 -0
- package/dist/{chunk-6npp3x3c.js → chunk-th4cwrpb.js} +2 -2
- package/dist/{chunk-6npp3x3c.js.map → chunk-th4cwrpb.js.map} +1 -1
- package/dist/{chunk-tamww15j.js → chunk-ttbwyxmh.js} +2 -2
- package/dist/{chunk-tamww15j.js.map → chunk-ttbwyxmh.js.map} +1 -1
- package/dist/chunk-w4c9kj70.js +13 -0
- package/dist/{chunk-n0qeyzqm.js.map → chunk-w4c9kj70.js.map} +2 -2
- package/dist/chunk-w9sabmb0.js +4 -0
- package/dist/chunk-w9sabmb0.js.map +10 -0
- package/dist/{chunk-8dty8c4p.js → chunk-yg3kj723.js} +3 -3
- package/dist/{chunk-8dty8c4p.js.map → chunk-yg3kj723.js.map} +2 -2
- package/dist/chunk-z1589fjk.js +4 -0
- package/dist/chunk-z1589fjk.js.map +10 -0
- package/dist/core/channel-result.d.ts +36 -0
- package/dist/core/channel-result.js +3 -0
- package/dist/core/channel-result.js.map +10 -0
- package/dist/core/decorated-transport.d.ts +30 -0
- package/dist/core/decorator-hooks.d.ts +17 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/provider-label.d.ts +7 -3
- package/dist/core/push-endpoint.d.ts +5 -0
- package/dist/core/push-types.d.ts +48 -14
- package/dist/core/sms-types.d.ts +12 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/whatsapp-types.d.ts +12 -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 +4 -0
- package/dist/index.js +5 -0
- 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 +1 -1
- 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 +1 -0
- package/dist/push.js +2 -2
- package/dist/push.js.map +3 -3
- package/dist/sms.d.ts +1 -0
- package/dist/sms.js +2 -2
- package/dist/sms.js.map +3 -3
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +1 -1
- 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.d.ts +32 -6
- package/dist/transports/fallback.js +2 -2
- package/dist/transports/fallback.js.map +1 -1
- package/dist/transports/fcm.d.ts +55 -0
- package/dist/transports/fcm.js +4 -0
- package/dist/transports/fcm.js.map +10 -0
- package/dist/transports/loops.js +2 -2
- package/dist/transports/loops.js.map +1 -1
- 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.js +2 -2
- package/dist/transports/msegat.js.map +1 -1
- package/dist/transports/plunk.js +2 -2
- package/dist/transports/plunk.js.map +1 -1
- 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 +1 -1
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.d.ts +28 -9
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -3
- 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.js +2 -2
- package/dist/transports/sndr.js.map +1 -1
- package/dist/transports/sparkpost.js +2 -2
- package/dist/transports/sparkpost.js.map +2 -2
- package/dist/transports/taqnyat-mail.js +2 -2
- package/dist/transports/taqnyat-mail.js.map +1 -1
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +1 -1
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +1 -1
- package/dist/transports/twilio-sms.js +2 -2
- package/dist/transports/twilio-sms.js.map +2 -2
- package/dist/transports/unifonic.d.ts +81 -0
- package/dist/transports/unifonic.js +3 -0
- package/dist/transports/unifonic.js.map +10 -0
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/transports/weighted-fallback.d.ts +11 -9
- package/dist/transports/weighted-fallback.js +2 -2
- package/dist/transports/weighted-fallback.js.map +3 -3
- package/dist/transports/whatsapp-cloud.js +2 -2
- package/dist/transports/whatsapp-cloud.js.map +1 -1
- package/dist/webhooks/brevo.js +2 -2
- package/dist/webhooks/brevo.js.map +1 -1
- package/dist/webhooks/mailgun.js +2 -2
- package/dist/webhooks/mailgun.js.map +1 -1
- package/dist/webhooks/postmark.js +2 -2
- package/dist/webhooks/postmark.js.map +1 -1
- package/dist/webhooks/resend.js +2 -2
- package/dist/webhooks/resend.js.map +1 -1
- package/dist/webhooks/sendgrid.js +2 -2
- package/dist/webhooks/sendgrid.js.map +1 -1
- package/dist/webhooks/ses.js +2 -2
- package/dist/webhooks/ses.js.map +1 -1
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/dist/webhooks/twilio-sms.d.ts +20 -0
- package/dist/webhooks/twilio-sms.js +3 -0
- package/dist/webhooks/twilio-sms.js.map +10 -0
- package/dist/webhooks/types.d.ts +37 -3
- package/dist/webhooks/types.js +3 -0
- package/dist/webhooks/types.js.map +9 -0
- package/dist/webhooks/unifonic.d.ts +13 -0
- package/dist/webhooks/unifonic.js +3 -0
- package/dist/webhooks/unifonic.js.map +10 -0
- package/dist/webhooks/whatsapp-cloud.d.ts +14 -0
- package/dist/webhooks/whatsapp-cloud.js +3 -0
- package/dist/webhooks/whatsapp-cloud.js.map +10 -0
- package/dist/webhooks.d.ts +10 -5
- package/dist/webhooks.js +11 -0
- package/dist/whatsapp.d.ts +1 -0
- package/dist/whatsapp.js +2 -2
- package/dist/whatsapp.js.map +3 -3
- package/package.json +26 -2
- package/site/content/docs/channels/hooks.mdx +6 -5
- package/site/content/docs/channels/index.mdx +2 -2
- package/site/content/docs/channels/push.mdx +27 -21
- package/site/content/docs/channels/sms.mdx +4 -1
- package/site/content/docs/decorators/fallback.mdx +41 -14
- package/site/content/docs/decorators/index.mdx +29 -12
- package/site/content/docs/decorators/retry.mdx +32 -9
- package/site/content/docs/decorators/weighted-fallback.mdx +27 -6
- package/site/content/docs/get-started/entrypoints.mdx +6 -5
- package/site/content/docs/get-started/installation.mdx +3 -2
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/guides/webhooks.mdx +35 -20
- package/site/content/docs/index.mdx +1 -1
- package/site/content/docs/quick-start/push.mdx +22 -5
- package/site/content/docs/reference/channel-result.mdx +82 -0
- package/site/content/docs/reference/exports.mdx +5 -3
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/push-options.mdx +44 -3
- package/site/content/docs/reference/sms-options.mdx +18 -2
- package/site/content/docs/reference/webhook-events.mdx +38 -14
- package/site/content/docs/reference/whatsapp-options.mdx +19 -1
- package/site/content/docs/transports/fcm.mdx +80 -0
- package/site/content/docs/transports/index.mdx +3 -1
- package/site/content/docs/transports/meta.json +3 -1
- package/site/content/docs/transports/unifonic.mdx +85 -0
- package/dist/chunk-0gqy32pe.js.map +0 -10
- package/dist/chunk-5915vbcj.js +0 -4
- package/dist/chunk-5915vbcj.js.map +0 -10
- package/dist/chunk-93vqxxj2.js +0 -4
- package/dist/chunk-b602dhck.js +0 -4
- package/dist/chunk-gp4fwrs8.js +0 -4
- package/dist/chunk-gp4fwrs8.js.map +0 -10
- package/dist/chunk-n0qeyzqm.js +0 -13
- package/dist/chunk-q8k3m6qe.js +0 -4
- package/dist/chunk-q8k3m6qe.js.map +0 -10
- package/dist/chunk-qtkd5bak.js +0 -4
- package/dist/chunk-yp311efr.js +0 -4
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Webhooks
|
|
3
|
-
description: Parse provider delivery events into
|
|
3
|
+
description: Parse provider delivery events into EmailEvent or DeliveryEvent shapes, with optional signature checks.
|
|
4
4
|
icon: Webhook
|
|
5
5
|
source: "src/webhooks.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Use webhooks when a provider posts delivery updates (delivered, bounced, and similar).
|
|
9
|
-
|
|
8
|
+
Use webhooks when a provider posts delivery updates (delivered, bounced, failed, and similar).
|
|
9
|
+
Email parsers return `EmailEvent[]`. SMS and WhatsApp parsers return `DeliveryEvent[]`.
|
|
10
|
+
Map email events with `toDeliveryEvent` when you want one ingest pipeline.
|
|
10
11
|
|
|
11
12
|
<Callout title="The one rule">
|
|
12
13
|
Verify the signature against the unmodified raw request body before you trust or act on the event.
|
|
@@ -53,7 +54,7 @@ const events = parse(JSON.parse(rawBody));
|
|
|
53
54
|
|
|
54
55
|
```ts
|
|
55
56
|
for (const event of events) {
|
|
56
|
-
if (event.type === "bounced") {
|
|
57
|
+
if (event.type === "bounced" || event.type === "failed") {
|
|
57
58
|
// suppress or alert using event.messageId / event.recipient
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -65,19 +66,32 @@ for (const event of events) {
|
|
|
65
66
|
|
|
66
67
|
## Provider entrypoints
|
|
67
68
|
|
|
68
|
-
| Import | Parse export | Verify export |
|
|
69
|
-
| ------ | ------------ | ------------- |
|
|
70
|
-
| `sently/webhooks/brevo` | `parse` | — |
|
|
71
|
-
| `sently/webhooks/mailgun` | `parse` | `verifySignature`, `verifyPayload` |
|
|
72
|
-
| `sently/webhooks/postmark` | `parse` | — |
|
|
73
|
-
| `sently/webhooks/resend` | `parse` | `verifySignature` |
|
|
74
|
-
| `sently/webhooks/sendgrid` | `parse` | — |
|
|
75
|
-
| `sently/webhooks/ses` | `parse` | — |
|
|
76
|
-
| `sently/webhooks/sndr` | `parse` | `verifySignature` |
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
| Import | Channel | Parse export | Verify export |
|
|
70
|
+
| ------ | ------- | ------------ | ------------- |
|
|
71
|
+
| `sently/webhooks/brevo` | Email | `parse` → `EmailEvent[]` | — |
|
|
72
|
+
| `sently/webhooks/mailgun` | Email | `parse` | `verifySignature`, `verifyPayload` |
|
|
73
|
+
| `sently/webhooks/postmark` | Email | `parse` | — |
|
|
74
|
+
| `sently/webhooks/resend` | Email | `parse` | `verifySignature` |
|
|
75
|
+
| `sently/webhooks/sendgrid` | Email | `parse` | — |
|
|
76
|
+
| `sently/webhooks/ses` | Email | `parse` | — |
|
|
77
|
+
| `sently/webhooks/sndr` | Email | `parse` | `verifySignature` |
|
|
78
|
+
| `sently/webhooks/twilio-sms` | SMS | `parse` → `DeliveryEvent[]` | `verifySignature` (`X-Twilio-Signature`) |
|
|
79
|
+
| `sently/webhooks/unifonic` | SMS | `parse` → `DeliveryEvent[]` | — |
|
|
80
|
+
| `sently/webhooks/whatsapp-cloud` | WhatsApp | `parse` → `DeliveryEvent[]` | `verifySignature` (`X-Hub-Signature-256`) |
|
|
81
|
+
|
|
82
|
+
The convenience barrel `sently/webhooks` re-exports named helpers such as `parseSndrWebhook`, `parseTwilioSmsWebhook`, and `toDeliveryEvent`.
|
|
79
83
|
Bundlers tree-shake unused names; Node/Deno without a bundler evaluate every re-export.
|
|
80
84
|
|
|
85
|
+
## SMS status callbacks
|
|
86
|
+
|
|
87
|
+
Twilio posts form fields (`MessageSid`, `MessageStatus`, `To`). Pass a parsed object or `URLSearchParams`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { parse } from "sently/webhooks/twilio-sms";
|
|
91
|
+
|
|
92
|
+
const events = parse(await request.formData().then((fd) => Object.fromEntries(fd)));
|
|
93
|
+
```
|
|
94
|
+
|
|
81
95
|
## SNDR signatures
|
|
82
96
|
|
|
83
97
|
SNDR signs with `X-Sndr-Signature: t=<unix>,v1=<hex>`.
|
|
@@ -99,7 +113,9 @@ Most frameworks parse JSON before your handler runs. Read the raw body first (`r
|
|
|
99
113
|
|
|
100
114
|
<Accordion title="Which providers are supported?">
|
|
101
115
|
|
|
102
|
-
Brevo, Mailgun, Postmark, Resend, SendGrid, SES,
|
|
116
|
+
Email: Brevo, Mailgun, Postmark, Resend, SendGrid, SES, SNDR.
|
|
117
|
+
SMS / WhatsApp: Twilio SMS, Unifonic, WhatsApp Cloud.
|
|
118
|
+
Import each from its `sently/webhooks/<provider>` subpath.
|
|
103
119
|
|
|
104
120
|
</Accordion>
|
|
105
121
|
|
|
@@ -107,14 +123,13 @@ Brevo, Mailgun, Postmark, Resend, SendGrid, SES, and SNDR. Import each from its
|
|
|
107
123
|
|
|
108
124
|
## Learn more
|
|
109
125
|
|
|
110
|
-
- [Webhook events](/docs/reference/webhook-events) — `EmailEvent`
|
|
111
|
-
- [SNDR transport](/docs/transports/sndr) — send path that produces those message ids
|
|
126
|
+
- [Webhook events](/docs/reference/webhook-events) — `EmailEvent` / `DeliveryEvent` fields
|
|
112
127
|
- [Exports](/docs/reference/exports) — package entrypoints
|
|
113
128
|
|
|
114
129
|
## Next
|
|
115
130
|
|
|
116
131
|
<Cards>
|
|
117
|
-
<Card title="Webhook events" description="Normalized
|
|
118
|
-
<Card title="
|
|
132
|
+
<Card title="Webhook events" description="Normalized event fields." href="/docs/reference/webhook-events" />
|
|
133
|
+
<Card title="Channel send result" description="Shared accepted mapping." href="/docs/reference/channel-result" />
|
|
119
134
|
<Card title="Exports" description="Public package entrypoints." href="/docs/reference/exports" />
|
|
120
135
|
</Cards>
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Push
|
|
3
|
-
description: Send a
|
|
3
|
+
description: Send a Web Push or FCM notification through a channel sender.
|
|
4
4
|
icon: Bell
|
|
5
5
|
source: "src/push.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Send a browser Web Push or FCM device notification through `createPushSender`.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">
|
|
11
|
+
Use `subscription` with `WebPushTransport`, or `token` with `FcmTransport` — not both shapes on the wrong transport.
|
|
12
|
+
</Callout>
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
9
15
|
|
|
10
16
|
<Steps>
|
|
11
|
-
<Step title="Create
|
|
17
|
+
<Step title="Create a Web Push sender">
|
|
12
18
|
|
|
13
19
|
```ts
|
|
14
20
|
import { createPushSender } from "sently/push";
|
|
@@ -27,10 +33,21 @@ const push = createPushSender({
|
|
|
27
33
|
<Step title="Send">
|
|
28
34
|
|
|
29
35
|
```ts
|
|
30
|
-
await push.send({
|
|
36
|
+
await push.send({
|
|
37
|
+
subscription,
|
|
38
|
+
title: "Update",
|
|
39
|
+
body: "Your report is ready.",
|
|
40
|
+
});
|
|
31
41
|
```
|
|
32
42
|
|
|
33
43
|
</Step>
|
|
34
44
|
</Steps>
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
For FCM device tokens, use `FcmTransport` and `token` instead of `subscription` — see [FCM](/docs/transports/fcm).
|
|
47
|
+
|
|
48
|
+
## Next
|
|
49
|
+
|
|
50
|
+
<Cards>
|
|
51
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
52
|
+
<Card title="FCM transport" href="/docs/transports/fcm" />
|
|
53
|
+
</Cards>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Channel send result
|
|
3
|
+
description: Map email, SMS, WhatsApp, and push results to a shared accepted shape.
|
|
4
|
+
icon: Boxes
|
|
5
|
+
source: "src/core/channel-result.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Adapters that span channels can normalize every send into
|
|
9
|
+
`{ messageId, provider, accepted }` without inventing per-provider glue.
|
|
10
|
+
Use this after `mailer.send`, `sms.send`, `whatsapp.send`, or `push.send`.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Keep channel-specific fields on the original result; use `toChannelSendResult` only for the shared slice.
|
|
14
|
+
</Callout>
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
<Steps>
|
|
19
|
+
<Step title="Send on any channel">
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createSmsSender } from "sently/sms";
|
|
23
|
+
import { toChannelSendResult } from "sently/channel-result";
|
|
24
|
+
import { UnifonicTransport } from "sently/transports/unifonic";
|
|
25
|
+
|
|
26
|
+
const sms = createSmsSender({
|
|
27
|
+
transport: new UnifonicTransport({
|
|
28
|
+
appSid: process.env.UNIFONIC_APPSID!,
|
|
29
|
+
senderId: "MyBrand",
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
const result = await sms.send({ to: "+15551234567", body: "Hi" });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
</Step>
|
|
36
|
+
<Step title="Normalize for your adapter">
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const shared = toChannelSendResult(result);
|
|
40
|
+
// { messageId, provider?, accepted }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</Step>
|
|
44
|
+
</Steps>
|
|
45
|
+
|
|
46
|
+
## ChannelSendResult
|
|
47
|
+
|
|
48
|
+
| Field | Type | Meaning |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `messageId` | `string` | Provider or client message id |
|
|
51
|
+
| `provider` | `string?` | Transport id when set |
|
|
52
|
+
| `accepted` | `boolean` | Provider accepted the send (not end-user delivery) |
|
|
53
|
+
|
|
54
|
+
## Mapping
|
|
55
|
+
|
|
56
|
+
| Channel result | `accepted` |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| Email `SendResult` | `accepted.length > 0` and `rejected.length === 0` |
|
|
59
|
+
| SMS / WhatsApp / Push | `status` in `accepted` / `queued` / `sending` / `sent` / `delivered` / `read` / `success` (case-insensitive) |
|
|
60
|
+
|
|
61
|
+
Import from `sently/channel-result` or the main `sently` barrel.
|
|
62
|
+
|
|
63
|
+
## Troubleshooting
|
|
64
|
+
|
|
65
|
+
<Accordions>
|
|
66
|
+
<Accordion title="Why is accepted true but the user never got the message?">
|
|
67
|
+
Acceptance means the provider took the message. Use [webhooks](/docs/guides/webhooks) for delivered / failed outcomes.
|
|
68
|
+
</Accordion>
|
|
69
|
+
</Accordions>
|
|
70
|
+
|
|
71
|
+
## Learn more
|
|
72
|
+
|
|
73
|
+
- [Webhook events](./webhook-events)
|
|
74
|
+
- [SMS options](./sms-options)
|
|
75
|
+
- [Exports](./exports)
|
|
76
|
+
|
|
77
|
+
## Next
|
|
78
|
+
|
|
79
|
+
<Cards>
|
|
80
|
+
<Card title="Webhook events" href="/docs/reference/webhook-events" />
|
|
81
|
+
<Card title="Exports" href="/docs/reference/exports" />
|
|
82
|
+
</Cards>
|
|
@@ -17,12 +17,13 @@ The main `sently` package is for shared types and factories — not every provid
|
|
|
17
17
|
|
|
18
18
|
| Import | Primary API |
|
|
19
19
|
| ------ | ----------- |
|
|
20
|
-
| `sently` | `createMailer`, `createSMTPMailer`, types, OAuth2, `SentlyError`, `consoleObserver`, `FallbackTransport`, `WeightedFallbackTransport`, `CloudflareEmailTransport` |
|
|
20
|
+
| `sently` | `createMailer`, `createSMTPMailer`, types, OAuth2, `SentlyError`, `consoleObserver`, `toChannelSendResult`, `FallbackTransport`, `WeightedFallbackTransport`, `CloudflareEmailTransport` |
|
|
21
21
|
| `sently/mailer` | `createMailer` only |
|
|
22
22
|
| `sently/smtp` | `createSMTPMailer` |
|
|
23
23
|
| `sently/sms` | `createSmsSender` |
|
|
24
24
|
| `sently/whatsapp` | `createWhatsAppSender` |
|
|
25
25
|
| `sently/push` | `createPushSender` |
|
|
26
|
+
| `sently/channel-result` | `toChannelSendResult`, `ChannelSendResult` |
|
|
26
27
|
| `sently/errors` | `SentlyError` hierarchy |
|
|
27
28
|
| `sently/idempotency` | `IdempotencyTransport` |
|
|
28
29
|
| `sently/dkim` | DKIM signing |
|
|
@@ -36,7 +37,7 @@ The main `sently` package is for shared types and factories — not every provid
|
|
|
36
37
|
|
|
37
38
|
| Import | Use |
|
|
38
39
|
| ------ | --- |
|
|
39
|
-
| `sently/transports/<name>` | One provider or
|
|
40
|
+
| `sently/transports/<name>` | One provider or decorator (e.g. `sndr`, `unifonic`, `fcm`, `retry`) |
|
|
40
41
|
|
|
41
42
|
HTTP providers such as SNDR, Resend, and Plunk are **not** re-exported from `sently`.
|
|
42
43
|
|
|
@@ -47,7 +48,8 @@ HTTP providers such as SNDR, Resend, and Plunk are **not** re-exported from `sen
|
|
|
47
48
|
| `sently/webhooks/<provider>` | One parser (+ verify helper when exported) |
|
|
48
49
|
| `sently/webhooks` | Convenience barrel of all named helpers |
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
Email: `brevo`, `mailgun`, `postmark`, `resend`, `sendgrid`, `ses`, `sndr`.
|
|
52
|
+
SMS / WhatsApp: `twilio-sms` (+ verify), `unifonic`, `whatsapp-cloud` (+ verify).
|
|
51
53
|
|
|
52
54
|
## Adapters
|
|
53
55
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Push options
|
|
3
|
-
description: Fields accepted by `PushSender.send
|
|
3
|
+
description: Fields accepted by `PushSender.send` for Web Push and FCM.
|
|
4
4
|
icon: Bell
|
|
5
5
|
source: "src/core/push-types.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
`PushOptions` is `WebPushOptions | FcmPushOptions`.
|
|
9
|
+
Pick the shape that matches your transport.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Pass `subscription` to `WebPushTransport` and `token` to `FcmTransport`.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Shared fields
|
|
16
|
+
|
|
8
17
|
| Field | Type | Required |
|
|
9
18
|
| --- | --- | --- |
|
|
10
|
-
| `subscription` | `PushSubscription` | yes |
|
|
11
19
|
| `title` | `string` | yes |
|
|
12
20
|
| `body` | `string` | yes |
|
|
13
21
|
| `data` | `Record<string, unknown>` | no |
|
|
@@ -15,6 +23,39 @@ source: "src/core/push-types.ts"
|
|
|
15
23
|
| `ttl` | `number` | no |
|
|
16
24
|
| `messageId` | `string` | no |
|
|
17
25
|
|
|
26
|
+
## Web Push
|
|
27
|
+
|
|
28
|
+
| Field | Type | Required |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `subscription` | `PushSubscription` | yes |
|
|
31
|
+
|
|
18
32
|
A subscription contains `endpoint`, `keys.p256dh`, and `keys.auth`.
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
## FCM
|
|
35
|
+
|
|
36
|
+
| Field | Type | Required |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `token` | `string` | yes |
|
|
39
|
+
| `image` | `string` | no |
|
|
40
|
+
|
|
41
|
+
FCM stringifies non-string `data` values before send.
|
|
42
|
+
|
|
43
|
+
## Result
|
|
44
|
+
|
|
45
|
+
| Field | Type | Notes |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `messageId` | `string` | Provider or client id |
|
|
48
|
+
| `status` | `string` | e.g. `"accepted"` |
|
|
49
|
+
| `response` | `string` | Often the HTTP status or FCM name |
|
|
50
|
+
| `provider` | `string?` | `"webpush"` / `"fcm"`; set by fallback |
|
|
51
|
+
| `providerIndex` | `number?` | Fallback chain index |
|
|
52
|
+
|
|
53
|
+
Map any channel result with [channel send result](./channel-result).
|
|
54
|
+
|
|
55
|
+
## Next
|
|
56
|
+
|
|
57
|
+
<Cards>
|
|
58
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
59
|
+
<Card title="FCM transport" href="/docs/transports/fcm" />
|
|
60
|
+
<Card title="Channel send result" href="/docs/reference/channel-result" />
|
|
61
|
+
</Cards>
|
|
@@ -12,6 +12,22 @@ source: "src/core/sms-types.ts"
|
|
|
12
12
|
| `from` | `string` | no |
|
|
13
13
|
| `messageId` | `string` | no |
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Result
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
| Field | Type | Notes |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `messageId` | `string` | Provider or client id |
|
|
20
|
+
| `to` | `string` | Recipient |
|
|
21
|
+
| `status` | `string` | e.g. `"accepted"`, `"queued"` |
|
|
22
|
+
| `response` | `string` | Provider text |
|
|
23
|
+
| `provider` | `string?` | Transport id; set by fallback |
|
|
24
|
+
| `providerIndex` | `number?` | Fallback chain index |
|
|
25
|
+
|
|
26
|
+
Map any channel result to `{ messageId, provider, accepted }` with [channel send result](./channel-result).
|
|
27
|
+
|
|
28
|
+
## Next
|
|
29
|
+
|
|
30
|
+
<Cards>
|
|
31
|
+
<Card title="SMS channel" href="/docs/channels/sms" />
|
|
32
|
+
<Card title="Channel send result" href="/docs/reference/channel-result" />
|
|
33
|
+
</Cards>
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Webhook events
|
|
3
|
-
description: Normalized EmailEvent
|
|
3
|
+
description: Normalized EmailEvent and DeliveryEvent fields from provider webhook parsers.
|
|
4
4
|
icon: Webhook
|
|
5
5
|
source: "src/webhooks/types.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Email parsers return `EmailEvent[]`. SMS and WhatsApp parsers return `DeliveryEvent[]`.
|
|
9
|
+
Use `toDeliveryEvent` to put email events on the same ingest path as SMS / WhatsApp.
|
|
10
10
|
|
|
11
11
|
<Callout title="The one rule">
|
|
12
12
|
Branch on `event.type` and keep provider-specific details in `event.raw` when you need them.
|
|
13
13
|
</Callout>
|
|
14
14
|
|
|
15
|
+
## DeliveryEvent
|
|
16
|
+
|
|
17
|
+
| Field | Type | Meaning |
|
|
18
|
+
| ----- | ---- | ------- |
|
|
19
|
+
| `channel` | `"email" \| "sms" \| "whatsapp" \| "push"` | Messaging channel |
|
|
20
|
+
| `provider` | `string` | Parser id (e.g. `"twilio-sms"`) |
|
|
21
|
+
| `type` | union below | Normalized lifecycle type |
|
|
22
|
+
| `messageId` | `string?` | Provider message id when present |
|
|
23
|
+
| `recipient` | `string?` | Address or phone when present |
|
|
24
|
+
| `timestamp` | `Date?` | Event time when present |
|
|
25
|
+
| `raw` | `unknown` | Original provider payload |
|
|
26
|
+
|
|
15
27
|
## EmailEvent
|
|
16
28
|
|
|
17
29
|
| Field | Type | Meaning |
|
|
@@ -25,15 +37,27 @@ Verify signatures in the [webhooks guide](/docs/guides/webhooks) before you proc
|
|
|
25
37
|
|
|
26
38
|
## Normalized `type` values
|
|
27
39
|
|
|
28
|
-
| Value | Meaning |
|
|
29
|
-
| ----- | ------- |
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
40
|
+
| Value | Channels | Meaning |
|
|
41
|
+
| ----- | -------- | ------- |
|
|
42
|
+
| `queued` | SMS | Accepted / queued by the provider |
|
|
43
|
+
| `sent` | SMS / WhatsApp | Handed to the network |
|
|
44
|
+
| `delivered` | All | Delivered (or mailbox accepted for email) |
|
|
45
|
+
| `failed` | SMS / WhatsApp | Permanent failure |
|
|
46
|
+
| `read` | SMS / WhatsApp | Read receipt |
|
|
47
|
+
| `bounced` | Email | Hard failure or bounce |
|
|
48
|
+
| `complained` | Email | Spam complaint |
|
|
49
|
+
| `opened` | Email | Open tracking |
|
|
50
|
+
| `clicked` | Email | Click tracking |
|
|
51
|
+
| `deferred` | Email | Temporary deferral |
|
|
52
|
+
| `unknown` | All | Unmapped or informational event |
|
|
53
|
+
|
|
54
|
+
## Mapping helpers
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { toDeliveryEvent } from "sently/webhooks";
|
|
58
|
+
|
|
59
|
+
const delivery = toDeliveryEvent(emailEvent);
|
|
60
|
+
```
|
|
37
61
|
|
|
38
62
|
## SNDR mapping
|
|
39
63
|
|
|
@@ -51,7 +75,7 @@ SNDR `data.email_id` becomes `messageId`. Recipients come from `data.to` or `dat
|
|
|
51
75
|
|
|
52
76
|
<Accordion title="Why is unsubscribed mapped to unknown?">
|
|
53
77
|
|
|
54
|
-
The shared `
|
|
78
|
+
The shared email `type` union has no unsubscribe value. Keep the vendor payload in `raw` if you need unsubscribe-specific handling.
|
|
55
79
|
|
|
56
80
|
</Accordion>
|
|
57
81
|
|
|
@@ -60,7 +84,7 @@ The shared `EmailEvent.type` union has no unsubscribe value. Keep the vendor pay
|
|
|
60
84
|
## Learn more
|
|
61
85
|
|
|
62
86
|
- [Webhooks guide](/docs/guides/webhooks) — verify + parse flow
|
|
63
|
-
- [
|
|
87
|
+
- [Channel send result](/docs/reference/channel-result) — send-time accepted mapping
|
|
64
88
|
|
|
65
89
|
## Next
|
|
66
90
|
|
|
@@ -12,4 +12,22 @@ source: "src/core/whatsapp-types.ts"
|
|
|
12
12
|
|
|
13
13
|
Both shapes accept optional `messageId`. Template components can include `header`, `body`, or `button` parameters.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Result
|
|
16
|
+
|
|
17
|
+
| Field | Type | Notes |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `messageId` | `string` | Provider or client id |
|
|
20
|
+
| `to` | `string` | Recipient |
|
|
21
|
+
| `status` | `string` | e.g. `"accepted"` |
|
|
22
|
+
| `response` | `string` | Provider text |
|
|
23
|
+
| `provider` | `string?` | Transport id; set by fallback |
|
|
24
|
+
| `providerIndex` | `number?` | Fallback chain index |
|
|
25
|
+
|
|
26
|
+
Map any channel result with [channel send result](./channel-result).
|
|
27
|
+
|
|
28
|
+
## Next
|
|
29
|
+
|
|
30
|
+
<Cards>
|
|
31
|
+
<Card title="WhatsApp channel" href="/docs/channels/whatsapp" />
|
|
32
|
+
<Card title="Channel send result" href="/docs/reference/channel-result" />
|
|
33
|
+
</Cards>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FCM
|
|
3
|
+
description: Send mobile push through Firebase Cloud Messaging HTTP v1.
|
|
4
|
+
icon: Bell
|
|
5
|
+
source: "src/transports/fcm.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Send device-token push through Firebase Cloud Messaging HTTP v1 for native Android and iOS clients.
|
|
9
|
+
Use [Web Push](./webpush) when you have a browser Push API subscription instead.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">Pass `token` in push options — not a Web Push `subscription`.</Callout>
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
<Steps>
|
|
16
|
+
<Step title="Configure the transport">
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createPushSender } from "sently/push";
|
|
20
|
+
import { FcmTransport } from "sently/transports/fcm";
|
|
21
|
+
|
|
22
|
+
const push = createPushSender({
|
|
23
|
+
transport: new FcmTransport({
|
|
24
|
+
projectId: process.env.FCM_PROJECT_ID!,
|
|
25
|
+
clientEmail: process.env.FCM_CLIENT_EMAIL!,
|
|
26
|
+
privateKey: process.env.FCM_PRIVATE_KEY!,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
</Step>
|
|
32
|
+
<Step title="Send to a device token">
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const result = await push.send({
|
|
36
|
+
token: deviceRegistrationToken,
|
|
37
|
+
title: "Hello",
|
|
38
|
+
body: "World",
|
|
39
|
+
});
|
|
40
|
+
console.log(result.messageId, result.status);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</Step>
|
|
44
|
+
</Steps>
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default or requirement |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `projectId` | `string` | required |
|
|
51
|
+
| `clientEmail` | `string` | service account email |
|
|
52
|
+
| `privateKey` | `string` | PEM private key (env / secrets only) |
|
|
53
|
+
| `getAccessToken` | `() => Promise<string>` | optional override for tests |
|
|
54
|
+
|
|
55
|
+
Literal `\n` sequences in `privateKey` env strings are normalized to newlines.
|
|
56
|
+
The transport mints a short-lived OAuth2 token with scope `firebase.messaging` — no Google SDK.
|
|
57
|
+
|
|
58
|
+
## Troubleshooting
|
|
59
|
+
|
|
60
|
+
<Accordions>
|
|
61
|
+
<Accordion title="Can I pass a browser subscription?">
|
|
62
|
+
No. `FcmTransport` requires `token`. Use `WebPushTransport` for VAPID subscriptions.
|
|
63
|
+
</Accordion>
|
|
64
|
+
<Accordion title="Where do I get the private key?">
|
|
65
|
+
Download a Firebase / GCP service account JSON and pass `project_id`, `client_email`, and `private_key` from that file via environment variables.
|
|
66
|
+
</Accordion>
|
|
67
|
+
</Accordions>
|
|
68
|
+
|
|
69
|
+
## Learn more
|
|
70
|
+
|
|
71
|
+
- [Push channel](/docs/channels/push)
|
|
72
|
+
- [Push options](/docs/reference/push-options)
|
|
73
|
+
- [Web Push](./webpush)
|
|
74
|
+
|
|
75
|
+
## Next
|
|
76
|
+
|
|
77
|
+
<Cards>
|
|
78
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
79
|
+
<Card title="Web Push" href="/docs/transports/webpush" />
|
|
80
|
+
</Cards>
|
|
@@ -63,9 +63,11 @@ Every import below is an exported package subpath.
|
|
|
63
63
|
| [Twilio SMS](./twilio-sms) | SMS | `sently/transports/twilio-sms` |
|
|
64
64
|
| [Taqnyat SMS](./taqnyat-sms) | SMS | `sently/transports/taqnyat-sms` |
|
|
65
65
|
| [Msegat](./msegat) | SMS | `sently/transports/msegat` |
|
|
66
|
+
| [Unifonic](./unifonic) | SMS | `sently/transports/unifonic` |
|
|
66
67
|
| [WhatsApp Cloud](./whatsapp-cloud) | WhatsApp | `sently/transports/whatsapp-cloud` |
|
|
67
68
|
| [Taqnyat WhatsApp](./taqnyat-whatsapp) | WhatsApp | `sently/transports/taqnyat-whatsapp` |
|
|
68
69
|
| [Web Push](./webpush) | Push | `sently/transports/webpush` |
|
|
70
|
+
| [FCM](./fcm) | Push | `sently/transports/fcm` |
|
|
69
71
|
|
|
70
72
|
For retry, fallback, preview, and idempotency wrappers, see [Decorators](../decorators).
|
|
71
73
|
|
|
@@ -85,7 +87,7 @@ For retry, fallback, preview, and idempotency wrappers, see [Decorators](../deco
|
|
|
85
87
|
No. A transport implements one channel contract. Choose a transport listed for the channel sender you use.
|
|
86
88
|
</Accordion>
|
|
87
89
|
<Accordion title="How do I add retries or fallback?">
|
|
88
|
-
Wrap
|
|
90
|
+
Wrap any channel transport with a [decorator](../decorators), then pass the wrapper to the matching sender.
|
|
89
91
|
</Accordion>
|
|
90
92
|
</Accordions>
|
|
91
93
|
|