sently 0.9.2 → 1.0.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 +86 -0
- package/README.md +39 -34
- package/SECURITY.md +59 -0
- 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/mailpit.d.ts +166 -0
- package/dist/transports/mailpit.js +3 -0
- package/dist/transports/mailpit.js.map +10 -0
- 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 +39 -2
- package/site/content/docs/ai/llms-txt.mdx +47 -2
- package/site/content/docs/channels/email.mdx +3 -0
- package/site/content/docs/channels/hooks.mdx +6 -5
- package/site/content/docs/channels/index.mdx +5 -3
- package/site/content/docs/channels/push.mdx +31 -21
- package/site/content/docs/channels/sms.mdx +4 -1
- package/site/content/docs/decorators/fallback.mdx +45 -13
- package/site/content/docs/decorators/index.mdx +31 -13
- package/site/content/docs/decorators/preview.mdx +52 -9
- 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 +11 -8
- package/site/content/docs/get-started/index.mdx +8 -2
- package/site/content/docs/get-started/installation.mdx +3 -2
- package/site/content/docs/get-started/introduction.mdx +7 -3
- package/site/content/docs/get-started/meta.json +4 -1
- package/site/content/docs/get-started/migrate-nodemailer.mdx +132 -5
- package/site/content/docs/get-started/non-goals.mdx +57 -0
- package/site/content/docs/get-started/stability.mdx +69 -0
- package/site/content/docs/get-started/support-matrix.mdx +62 -0
- package/site/content/docs/guides/compare.mdx +87 -0
- package/site/content/docs/guides/failover.mdx +121 -0
- package/site/content/docs/guides/index.mdx +2 -0
- package/site/content/docs/guides/meta.json +2 -0
- package/site/content/docs/guides/security.mdx +38 -8
- package/site/content/docs/guides/webhooks.mdx +35 -20
- package/site/content/docs/index.mdx +78 -4
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/channel-result.mdx +82 -0
- package/site/content/docs/reference/exports.mdx +9 -4
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/push-options.mdx +58 -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 +84 -0
- package/site/content/docs/transports/index.mdx +11 -3
- package/site/content/docs/transports/mailpit.mdx +123 -0
- package/site/content/docs/transports/meta.json +4 -1
- package/site/content/docs/transports/smtp.mdx +61 -22
- 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
- package/site/content/docs/quick-start/email.mdx +0 -40
- package/site/content/docs/quick-start/index.mdx +0 -15
- package/site/content/docs/quick-start/meta.json +0 -5
- package/site/content/docs/quick-start/push.mdx +0 -36
- package/site/content/docs/quick-start/sms.mdx +0 -36
- package/site/content/docs/quick-start/whatsapp.mdx +0 -38
|
@@ -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,15 +1,89 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: sently handbook
|
|
3
|
-
description: Channel-
|
|
3
|
+
description: Channel-delivery for email, SMS, WhatsApp, and push — one sender shape as channels grow.
|
|
4
4
|
icon: BookOpen
|
|
5
5
|
source: "README.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
sently is the channel-delivery layer for the order confirmation email, the OTP SMS, and the device push that follow.
|
|
9
|
+
Apps call a channel sender; providers live in transports you can swap without rewriting send sites.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Use `createMailer`, `createSmsSender`, `createWhatsAppSender`, or `createPushSender` in app code — never a vendor SDK as your application API.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
<Steps>
|
|
18
|
+
<Step title="Install">
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add sently
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step>
|
|
25
|
+
<Step title="Send with a channel sender">
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createMailer } from "sently/mailer";
|
|
29
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
30
|
+
|
|
31
|
+
const mailer = await createMailer({
|
|
32
|
+
transport: new ResendTransport({
|
|
33
|
+
apiKey: process.env.RESEND_API_KEY!,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const result = await mailer.send({
|
|
38
|
+
from: "hello@example.com",
|
|
39
|
+
to: "person@example.com",
|
|
40
|
+
subject: "Welcome",
|
|
41
|
+
text: "Thanks for joining.",
|
|
42
|
+
});
|
|
43
|
+
console.log(result.messageId);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
</Step>
|
|
47
|
+
</Steps>
|
|
48
|
+
|
|
49
|
+
## Channels
|
|
50
|
+
|
|
51
|
+
| Channel | Sender | Start here |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Email | `createMailer` / `createSMTPMailer` | [Email](/docs/channels/email) |
|
|
54
|
+
| SMS | `createSmsSender` | [SMS](/docs/channels/sms) |
|
|
55
|
+
| WhatsApp | `createWhatsAppSender` | [WhatsApp](/docs/channels/whatsapp) |
|
|
56
|
+
| Push | `createPushSender` | [Push](/docs/channels/push) |
|
|
57
|
+
|
|
58
|
+
Email factories are asynchronous. SMS, WhatsApp, and push factories return the sender synchronously; every `send` is async.
|
|
59
|
+
|
|
60
|
+
## Where next
|
|
61
|
+
|
|
62
|
+
| Path | When |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| [Get started](/docs/get-started) | Install, entrypoints, runtimes, migrate from Nodemailer |
|
|
65
|
+
| [Channels](/docs/channels) | Full sender docs + lifecycle hooks |
|
|
66
|
+
| [Transports](/docs/transports) | Provider list by channel |
|
|
67
|
+
| [Failover](/docs/guides/failover) | Retry inside a provider, then fail over |
|
|
68
|
+
| [Support matrix](/docs/get-started/support-matrix) | Supported vs Available for production |
|
|
69
|
+
| [Compare](/docs/guides/compare) | vs vendor SDKs, Nodemailer, Novu / Knock / Courier |
|
|
70
|
+
| [Stability](/docs/get-started/stability) | What is frozen at 1.x |
|
|
71
|
+
|
|
72
|
+
## Troubleshooting
|
|
73
|
+
|
|
74
|
+
<Accordions>
|
|
75
|
+
<Accordion title="Is sently a notification platform like Novu or Knock?">
|
|
76
|
+
No. It is the delivery layer. Preference centers, digests, workflows, and in-app inboxes sit on top — custom code or those tools. See [Compare](/docs/guides/compare).
|
|
77
|
+
</Accordion>
|
|
78
|
+
<Accordion title="Where should I send first?">
|
|
79
|
+
Start with [Email](/docs/channels/email) or [Installation](/docs/get-started/installation), then open the channel page for SMS, WhatsApp, or push when you add that channel.
|
|
80
|
+
</Accordion>
|
|
81
|
+
</Accordions>
|
|
82
|
+
|
|
83
|
+
## Next
|
|
9
84
|
|
|
10
85
|
<Cards>
|
|
11
86
|
<Card title="Get started" href="/docs/get-started" />
|
|
12
|
-
<Card title="Quick start" href="/docs/quick-start" />
|
|
13
87
|
<Card title="Channels" href="/docs/channels" />
|
|
14
|
-
<Card title="
|
|
88
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
15
89
|
</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`, `mailpit`, `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
|
|
|
@@ -73,11 +75,14 @@ Provider transports live on `sently/transports/*` so bare Node/Deno imports of `
|
|
|
73
75
|
## Learn more
|
|
74
76
|
|
|
75
77
|
- [Entrypoints](/docs/get-started/entrypoints) — which import to pick first
|
|
76
|
-
- [
|
|
78
|
+
- [Stability policy](/docs/get-started/stability) — frozen entrypoints at 1.x
|
|
79
|
+
- [Support matrix](/docs/get-started/support-matrix) — Supported vs Available
|
|
80
|
+
- [Bundle size](/docs/guides/bundle-size) — measured stacks (supporting for Workers)
|
|
77
81
|
|
|
78
82
|
## Next
|
|
79
83
|
|
|
80
84
|
<Cards>
|
|
81
85
|
<Card title="Entrypoints" description="Choose a channel import path." href="/docs/get-started/entrypoints" />
|
|
86
|
+
<Card title="Support matrix" description="Production support promise." href="/docs/get-started/support-matrix" />
|
|
82
87
|
<Card title="Transports" description="Provider list by channel." href="/docs/transports" />
|
|
83
88
|
</Cards>
|
|
@@ -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,53 @@ 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
|
+
## Troubleshooting
|
|
56
|
+
|
|
57
|
+
<Accordions>
|
|
58
|
+
<Accordion title="Can I send both subscription and token?">
|
|
59
|
+
No. Each call is either Web Push (`subscription`) or FCM (`token`). Match the shape to the transport.
|
|
60
|
+
</Accordion>
|
|
61
|
+
</Accordions>
|
|
62
|
+
|
|
63
|
+
## Learn more
|
|
64
|
+
|
|
65
|
+
- [Push channel](/docs/channels/push)
|
|
66
|
+
- [Stability policy](/docs/get-started/stability) — union shape is frozen at 1.x
|
|
67
|
+
- [Channel send result](./channel-result)
|
|
68
|
+
|
|
69
|
+
## Next
|
|
70
|
+
|
|
71
|
+
<Cards>
|
|
72
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
73
|
+
<Card title="FCM transport" href="/docs/transports/fcm" />
|
|
74
|
+
<Card title="Channel send result" href="/docs/reference/channel-result" />
|
|
75
|
+
</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,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FCM
|
|
3
|
+
description: Send mobile push through Firebase Cloud Messaging (current HTTP API).
|
|
4
|
+
icon: Bell
|
|
5
|
+
source: "src/transports/fcm.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Send device-token push through Firebase Cloud Messaging for native Android and iOS clients.
|
|
9
|
+
The transport uses the current FCM HTTP API with a service-account JWT — no Google SDK.
|
|
10
|
+
Use [Web Push](./webpush) when you have a browser Push API subscription instead.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">Pass `token` in push options — not a Web Push `subscription`.</Callout>
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
<Steps>
|
|
17
|
+
<Step title="Configure the transport">
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createPushSender } from "sently/push";
|
|
21
|
+
import { FcmTransport } from "sently/transports/fcm";
|
|
22
|
+
|
|
23
|
+
const push = createPushSender({
|
|
24
|
+
transport: new FcmTransport({
|
|
25
|
+
projectId: process.env.FCM_PROJECT_ID!,
|
|
26
|
+
clientEmail: process.env.FCM_CLIENT_EMAIL!,
|
|
27
|
+
privateKey: process.env.FCM_PRIVATE_KEY!,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
</Step>
|
|
33
|
+
<Step title="Verify credentials, then send">
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const check = await push.verify();
|
|
37
|
+
console.log(check.ok, check.message);
|
|
38
|
+
|
|
39
|
+
const result = await push.send({
|
|
40
|
+
token: deviceRegistrationToken,
|
|
41
|
+
title: "Hello",
|
|
42
|
+
body: "World",
|
|
43
|
+
});
|
|
44
|
+
console.log(result.messageId, result.status, result.provider);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
</Step>
|
|
48
|
+
</Steps>
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
| Option | Type | Default or requirement |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `projectId` | `string` | required |
|
|
55
|
+
| `clientEmail` | `string` | service account email |
|
|
56
|
+
| `privateKey` | `string` | PEM private key (env / secrets only) |
|
|
57
|
+
| `getAccessToken` | `() => Promise<string>` | optional override for tests |
|
|
58
|
+
|
|
59
|
+
Literal `\n` sequences in `privateKey` env strings are normalized to newlines.
|
|
60
|
+
The transport mints a short-lived OAuth2 token with scope `firebase.messaging` — no Google SDK.
|
|
61
|
+
|
|
62
|
+
## Troubleshooting
|
|
63
|
+
|
|
64
|
+
<Accordions>
|
|
65
|
+
<Accordion title="Can I pass a browser subscription?">
|
|
66
|
+
No. `FcmTransport` requires `token`. Use `WebPushTransport` for VAPID subscriptions.
|
|
67
|
+
</Accordion>
|
|
68
|
+
<Accordion title="Where do I get the private key?">
|
|
69
|
+
Download a Firebase / GCP service account JSON and pass `project_id`, `client_email`, and `private_key` from that file via environment variables.
|
|
70
|
+
</Accordion>
|
|
71
|
+
</Accordions>
|
|
72
|
+
|
|
73
|
+
## Learn more
|
|
74
|
+
|
|
75
|
+
- [Push channel](/docs/channels/push)
|
|
76
|
+
- [Push options](/docs/reference/push-options)
|
|
77
|
+
- [Web Push](./webpush)
|
|
78
|
+
|
|
79
|
+
## Next
|
|
80
|
+
|
|
81
|
+
<Cards>
|
|
82
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
83
|
+
<Card title="Web Push" href="/docs/transports/webpush" />
|
|
84
|
+
</Cards>
|