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,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Bulk sending
|
|
3
|
+
description: Send multiple email messages with concurrency and batch-aware transports.
|
|
4
|
+
icon: Layers
|
|
5
|
+
source: "src/mailer.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">Use `sendBulk` for independent messages; it reports per-message failures instead of throwing the entire batch result away.</Callout>
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const result = await mailer.sendBulk(messages, { concurrency: 5, stopOnError: false });
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Resend and SendGrid can use transport batch support; messages with attachments are sent individually.
|
|
15
|
+
|
|
16
|
+
<Accordions><Accordion title="How are errors represented?">Each result entry is either `{ status: "sent", result }` or `{ status: "failed", error }`.</Accordion></Accordions>
|
|
17
|
+
|
|
18
|
+
<Cards><Card title="Mail options" href="/docs/reference/mail-options" /></Cards>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Vendor OTP helpers
|
|
3
|
+
description: Use OTP helpers only on concrete SMS transport instances.
|
|
4
|
+
icon: BadgeCheck
|
|
5
|
+
source: "src/transports/msegat.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">OTP methods are provider extras; they are not part of `SmsSender` or `SmsTransport`.</Callout>
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const msegat = new MsegatTransport({ userName: "...", apiKey: "...", sender: "MyBrand" });
|
|
12
|
+
const otp = await msegat.sendOtp({ to: "+9665...", lang: "En" });
|
|
13
|
+
await msegat.verifyOtp({ id: otp.id, code: "1234", lang: "En" });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Taqnyat SMS also exposes `sendOtp` and `verifyOtp` on its transport.
|
|
17
|
+
|
|
18
|
+
<Cards><Card title="Msegat" href="/docs/transports/msegat" /><Card title="Taqnyat SMS" href="/docs/transports/taqnyat-sms" /></Cards>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Webhooks
|
|
3
|
+
description: Parse provider delivery events into a shared EmailEvent shape, with optional signature checks.
|
|
4
|
+
icon: Webhook
|
|
5
|
+
source: "src/webhooks.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Use webhooks when a provider posts delivery updates (delivered, bounced, and similar).
|
|
9
|
+
sently turns each provider payload into `EmailEvent[]` so your app can share one handler.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Verify the signature against the unmodified raw request body before you trust or act on the event.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
<Steps>
|
|
18
|
+
|
|
19
|
+
<Step>
|
|
20
|
+
### Import one provider subpath
|
|
21
|
+
|
|
22
|
+
Prefer `sently/webhooks/<provider>` so runtimes without a bundler do not load every parser.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { parse, verifySignature } from "sently/webhooks/sndr";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
</Step>
|
|
29
|
+
|
|
30
|
+
<Step>
|
|
31
|
+
### Verify, then parse
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const rawBody = await request.text(); // exact bytes as a string
|
|
35
|
+
const signature = request.headers.get("x-sndr-signature") ?? "";
|
|
36
|
+
|
|
37
|
+
const ok = await verifySignature(
|
|
38
|
+
rawBody,
|
|
39
|
+
signature,
|
|
40
|
+
process.env.SNDR_WEBHOOK_SECRET!,
|
|
41
|
+
);
|
|
42
|
+
if (!ok) {
|
|
43
|
+
return new Response("bad signature", { status: 400 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const events = parse(JSON.parse(rawBody));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
</Step>
|
|
50
|
+
|
|
51
|
+
<Step>
|
|
52
|
+
### Handle normalized events
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
for (const event of events) {
|
|
56
|
+
if (event.type === "bounced") {
|
|
57
|
+
// suppress or alert using event.messageId / event.recipient
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
</Step>
|
|
63
|
+
|
|
64
|
+
</Steps>
|
|
65
|
+
|
|
66
|
+
## Provider entrypoints
|
|
67
|
+
|
|
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
|
+
The convenience barrel `sently/webhooks` re-exports named helpers such as `parseSndrWebhook` and `verifySndrSignature`.
|
|
79
|
+
Bundlers tree-shake unused names; Node/Deno without a bundler evaluate every re-export.
|
|
80
|
+
|
|
81
|
+
## SNDR signatures
|
|
82
|
+
|
|
83
|
+
SNDR signs with `X-Sndr-Signature: t=<unix>,v1=<hex>`.
|
|
84
|
+
The digest is HMAC-SHA256 over `` `${t}.${rawBody}` ``.
|
|
85
|
+
|
|
86
|
+
**Consequence:** Re-serializing JSON changes whitespace and key order and breaks verification — always use the raw body string.
|
|
87
|
+
|
|
88
|
+
Default timestamp tolerance is 300 seconds. Pass `{ toleranceSeconds: 0 }` to disable the freshness check.
|
|
89
|
+
|
|
90
|
+
## Troubleshooting
|
|
91
|
+
|
|
92
|
+
<Accordions>
|
|
93
|
+
|
|
94
|
+
<Accordion title="Signature verifies in docs but fails in production">
|
|
95
|
+
|
|
96
|
+
Most frameworks parse JSON before your handler runs. Read the raw body first (`request.text()`, `express.raw()`, or equivalent), verify, then `JSON.parse`.
|
|
97
|
+
|
|
98
|
+
</Accordion>
|
|
99
|
+
|
|
100
|
+
<Accordion title="Which providers are supported?">
|
|
101
|
+
|
|
102
|
+
Brevo, Mailgun, Postmark, Resend, SendGrid, SES, and SNDR. Import each from its `sently/webhooks/<provider>` subpath.
|
|
103
|
+
|
|
104
|
+
</Accordion>
|
|
105
|
+
|
|
106
|
+
</Accordions>
|
|
107
|
+
|
|
108
|
+
## Learn more
|
|
109
|
+
|
|
110
|
+
- [Webhook events](/docs/reference/webhook-events) — `EmailEvent` fields and type unions
|
|
111
|
+
- [SNDR transport](/docs/transports/sndr) — send path that produces those message ids
|
|
112
|
+
- [Exports](/docs/reference/exports) — package entrypoints
|
|
113
|
+
|
|
114
|
+
## Next
|
|
115
|
+
|
|
116
|
+
<Cards>
|
|
117
|
+
<Card title="Webhook events" description="Normalized EmailEvent fields." href="/docs/reference/webhook-events" />
|
|
118
|
+
<Card title="SNDR" description="Send email through SNDR." href="/docs/transports/sndr" />
|
|
119
|
+
<Card title="Exports" description="Public package entrypoints." href="/docs/reference/exports" />
|
|
120
|
+
</Cards>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Web Push interoperability
|
|
3
|
+
description: Use subscriptions from the browser Push API with the Web Push transport.
|
|
4
|
+
icon: BellRing
|
|
5
|
+
source: "src/transports/webpush.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">Pass the Push API subscription endpoint and both keys exactly as the browser returned them.</Callout>
|
|
9
|
+
|
|
10
|
+
The transport accepts the standard `endpoint`, `keys.p256dh`, and `keys.auth` shape and encrypts payloads using Web Push standards.
|
|
11
|
+
|
|
12
|
+
## Troubleshooting
|
|
13
|
+
|
|
14
|
+
<Accordions><Accordion title="Can I allow a private push relay?">Provide exact hostnames with `allowedEndpointHosts`.</Accordion></Accordions>
|
|
15
|
+
|
|
16
|
+
## Next
|
|
17
|
+
|
|
18
|
+
<Cards><Card title="Web Push transport" href="/docs/transports/webpush" /></Cards>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: sently handbook
|
|
3
|
+
description: Channel-first messaging for email, SMS, WhatsApp, and web push.
|
|
4
|
+
icon: BookOpen
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Use a channel sender in application code and put provider-specific configuration in a transport.
|
|
9
|
+
|
|
10
|
+
<Cards>
|
|
11
|
+
<Card title="Get started" href="/docs/get-started" />
|
|
12
|
+
<Card title="Quick start" href="/docs/quick-start" />
|
|
13
|
+
<Card title="Channels" href="/docs/channels" />
|
|
14
|
+
<Card title="Transports" href="/docs/transports" />
|
|
15
|
+
</Cards>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Email
|
|
3
|
+
description: Send email through an HTTP transport.
|
|
4
|
+
icon: Mail
|
|
5
|
+
source: "src/mailer.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">`createMailer` is asynchronous; await it before sending.</Callout>
|
|
9
|
+
|
|
10
|
+
<Steps>
|
|
11
|
+
<Step title="Create a transport-backed mailer">
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createMailer } from "sently/mailer";
|
|
15
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
16
|
+
|
|
17
|
+
const mailer = await createMailer({
|
|
18
|
+
transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
</Step>
|
|
23
|
+
<Step title="Send email">
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
await mailer.send({
|
|
27
|
+
from: "hello@example.com",
|
|
28
|
+
to: "person@example.com",
|
|
29
|
+
subject: "Welcome",
|
|
30
|
+
text: "Thanks for joining.",
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</Step>
|
|
35
|
+
</Steps>
|
|
36
|
+
|
|
37
|
+
<Cards>
|
|
38
|
+
<Card title="Email channel" href="/docs/channels/email" />
|
|
39
|
+
<Card title="Resend transport" href="/docs/transports/resend" />
|
|
40
|
+
</Cards>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Quick start
|
|
3
|
+
description: Send your first message on each channel.
|
|
4
|
+
icon: Zap
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Pick a channel and send in a few lines. Same model every time — sender, transport, send.
|
|
9
|
+
|
|
10
|
+
<Cards>
|
|
11
|
+
<Card title="Email" href="/docs/quick-start/email" />
|
|
12
|
+
<Card title="SMS" href="/docs/quick-start/sms" />
|
|
13
|
+
<Card title="WhatsApp" href="/docs/quick-start/whatsapp" />
|
|
14
|
+
<Card title="Push" href="/docs/quick-start/push" />
|
|
15
|
+
</Cards>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Push
|
|
3
|
+
description: Send a browser notification through a channel sender.
|
|
4
|
+
icon: Bell
|
|
5
|
+
source: "src/push.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">Store the browser subscription and send it back unchanged as `subscription`.</Callout>
|
|
9
|
+
|
|
10
|
+
<Steps>
|
|
11
|
+
<Step title="Create the sender">
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createPushSender } from "sently/push";
|
|
15
|
+
import { WebPushTransport } from "sently/transports/webpush";
|
|
16
|
+
|
|
17
|
+
const push = createPushSender({
|
|
18
|
+
transport: new WebPushTransport({
|
|
19
|
+
vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,
|
|
20
|
+
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,
|
|
21
|
+
subject: "mailto:you@example.com",
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
</Step>
|
|
27
|
+
<Step title="Send">
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
await push.send({ subscription, title: "Update", body: "Your report is ready." });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
</Step>
|
|
34
|
+
</Steps>
|
|
35
|
+
|
|
36
|
+
<Cards><Card title="Push channel" href="/docs/channels/push" /></Cards>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SMS
|
|
3
|
+
description: Send an SMS through a channel sender.
|
|
4
|
+
icon: MessageSquareText
|
|
5
|
+
source: "src/sms.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">Create the SMS sender once, then call `send` with `to` and `body`.</Callout>
|
|
9
|
+
|
|
10
|
+
<Steps>
|
|
11
|
+
<Step title="Create the sender">
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createSmsSender } from "sently/sms";
|
|
15
|
+
import { TwilioSmsTransport } from "sently/transports/twilio-sms";
|
|
16
|
+
|
|
17
|
+
const sms = createSmsSender({
|
|
18
|
+
transport: new TwilioSmsTransport({
|
|
19
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID!,
|
|
20
|
+
authToken: process.env.TWILIO_AUTH_TOKEN!,
|
|
21
|
+
from: "+15557654321",
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
</Step>
|
|
27
|
+
<Step title="Send">
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
await sms.send({ to: "+15551234567", body: "Your code is ready." });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
</Step>
|
|
34
|
+
</Steps>
|
|
35
|
+
|
|
36
|
+
<Cards><Card title="SMS channel" href="/docs/channels/sms" /></Cards>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WhatsApp
|
|
3
|
+
description: Send a WhatsApp message through a channel sender.
|
|
4
|
+
icon: MessagesSquare
|
|
5
|
+
source: "src/whatsapp.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Callout title="The one rule">Use a template outside the open customer-service window; use free text only inside it.</Callout>
|
|
9
|
+
|
|
10
|
+
<Steps>
|
|
11
|
+
<Step title="Create the sender">
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createWhatsAppSender } from "sently/whatsapp";
|
|
15
|
+
import { WhatsAppCloudTransport } from "sently/transports/whatsapp-cloud";
|
|
16
|
+
|
|
17
|
+
const whatsapp = createWhatsAppSender({
|
|
18
|
+
transport: new WhatsAppCloudTransport({
|
|
19
|
+
accessToken: process.env.WHATSAPP_TOKEN!,
|
|
20
|
+
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
</Step>
|
|
26
|
+
<Step title="Send a template">
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
await whatsapp.send({
|
|
30
|
+
to: "15551234567",
|
|
31
|
+
template: { name: "welcome", language: "en_US" },
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
</Step>
|
|
36
|
+
</Steps>
|
|
37
|
+
|
|
38
|
+
<Cards><Card title="WhatsApp channel" href="/docs/channels/whatsapp" /></Cards>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Errors
|
|
3
|
+
description: Handle stable sently error codes and provider-specific error instances.
|
|
4
|
+
icon: CircleAlert
|
|
5
|
+
source: "src/core/errors.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
All library transport and protocol failures derive from `SentlyError`; use `sentlyCode` for stable handling.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { SentlyError } from "sently/errors";
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await mailer.send(message);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof SentlyError) console.error(error.sentlyCode);
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Provider error classes also retain provider response details where their transport exposes them.
|
|
21
|
+
|
|
22
|
+
<Cards><Card title="Security" href="/docs/guides/security" /></Cards>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Exports
|
|
3
|
+
description: Public package entrypoints and what each one is for.
|
|
4
|
+
icon: Boxes
|
|
5
|
+
source: "package.json"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Import the channel sender and one transport subpath for the smallest install surface.
|
|
9
|
+
The main `sently` package is for shared types and factories — not every provider.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Prefer `sently/mailer` + `sently/transports/<provider>` (or the matching SMS / WhatsApp / push
|
|
13
|
+
entrypoints) over pulling providers from the main barrel.
|
|
14
|
+
</Callout>
|
|
15
|
+
|
|
16
|
+
## Channel and core
|
|
17
|
+
|
|
18
|
+
| Import | Primary API |
|
|
19
|
+
| ------ | ----------- |
|
|
20
|
+
| `sently` | `createMailer`, `createSMTPMailer`, types, OAuth2, `SentlyError`, `consoleObserver`, `FallbackTransport`, `WeightedFallbackTransport`, `CloudflareEmailTransport` |
|
|
21
|
+
| `sently/mailer` | `createMailer` only |
|
|
22
|
+
| `sently/smtp` | `createSMTPMailer` |
|
|
23
|
+
| `sently/sms` | `createSmsSender` |
|
|
24
|
+
| `sently/whatsapp` | `createWhatsAppSender` |
|
|
25
|
+
| `sently/push` | `createPushSender` |
|
|
26
|
+
| `sently/errors` | `SentlyError` hierarchy |
|
|
27
|
+
| `sently/idempotency` | `IdempotencyTransport` |
|
|
28
|
+
| `sently/dkim` | DKIM signing |
|
|
29
|
+
| `sently/react` | `reactPlugin` |
|
|
30
|
+
| `sently/plugins/template` | template plugin |
|
|
31
|
+
| `sently/auth/oauth2` | `OAuth2Client` |
|
|
32
|
+
| `sently/pool` | `SMTPPool` |
|
|
33
|
+
| `sently/observability` | `consoleObserver` |
|
|
34
|
+
|
|
35
|
+
## Transports
|
|
36
|
+
|
|
37
|
+
| Import | Use |
|
|
38
|
+
| ------ | --- |
|
|
39
|
+
| `sently/transports/<name>` | One provider or email decorator (e.g. `sndr`, `resend`, `twilio-sms`) |
|
|
40
|
+
|
|
41
|
+
HTTP providers such as SNDR, Resend, and Plunk are **not** re-exported from `sently`.
|
|
42
|
+
|
|
43
|
+
## Webhooks
|
|
44
|
+
|
|
45
|
+
| Import | Use |
|
|
46
|
+
| ------ | --- |
|
|
47
|
+
| `sently/webhooks/<provider>` | One parser (+ verify helper when exported) |
|
|
48
|
+
| `sently/webhooks` | Convenience barrel of all named helpers |
|
|
49
|
+
|
|
50
|
+
Providers: `brevo`, `mailgun`, `postmark`, `resend`, `sendgrid`, `ses`, `sndr`.
|
|
51
|
+
|
|
52
|
+
## Adapters
|
|
53
|
+
|
|
54
|
+
| Import | Use |
|
|
55
|
+
| ------ | --- |
|
|
56
|
+
| `sently/adapters/node` | Node TCP/TLS |
|
|
57
|
+
| `sently/adapters/bun` | Bun TCP/TLS |
|
|
58
|
+
| `sently/adapters/deno` | Deno TCP/TLS |
|
|
59
|
+
| `sently/adapters/cf` | Cloudflare `cloudflare:sockets` |
|
|
60
|
+
|
|
61
|
+
## Troubleshooting
|
|
62
|
+
|
|
63
|
+
<Accordions>
|
|
64
|
+
|
|
65
|
+
<Accordion title="Why is SndrTransport missing from import sently?">
|
|
66
|
+
|
|
67
|
+
Provider transports live on `sently/transports/*` so bare Node/Deno imports of `sently` do not load every HTTP client. Use `sently/transports/sndr`.
|
|
68
|
+
|
|
69
|
+
</Accordion>
|
|
70
|
+
|
|
71
|
+
</Accordions>
|
|
72
|
+
|
|
73
|
+
## Learn more
|
|
74
|
+
|
|
75
|
+
- [Entrypoints](/docs/get-started/entrypoints) — which import to pick first
|
|
76
|
+
- [Bundle size](/docs/guides/bundle-size) — measured stacks
|
|
77
|
+
|
|
78
|
+
## Next
|
|
79
|
+
|
|
80
|
+
<Cards>
|
|
81
|
+
<Card title="Entrypoints" description="Choose a channel import path." href="/docs/get-started/entrypoints" />
|
|
82
|
+
<Card title="Transports" description="Provider list by channel." href="/docs/transports" />
|
|
83
|
+
</Cards>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reference
|
|
3
|
+
description: Public types, exports, and normalized events.
|
|
4
|
+
icon: FolderOpen
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Public types, exports, and normalized events.
|
|
9
|
+
|
|
10
|
+
<Cards>
|
|
11
|
+
<Card title="Exports" href="/docs/reference/exports" />
|
|
12
|
+
<Card title="Mail options" href="/docs/reference/mail-options" />
|
|
13
|
+
<Card title="Transport contracts" href="/docs/reference/transport-contracts" />
|
|
14
|
+
<Card title="Errors" href="/docs/reference/errors" />
|
|
15
|
+
</Cards>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mail options
|
|
3
|
+
description: Fields accepted by `mailer.send` and `mailer.sendBulk`.
|
|
4
|
+
icon: Mail
|
|
5
|
+
source: "src/core/types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
| Field | Type |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `from`, `to` | `AddressInput` |
|
|
11
|
+
| `subject` | `string` |
|
|
12
|
+
| `text`, `html` | `string` |
|
|
13
|
+
| `cc`, `bcc`, `replyTo` | `AddressInput` |
|
|
14
|
+
| `attachments` | `Attachment[]` |
|
|
15
|
+
| `headers` | `Record<string, string>` |
|
|
16
|
+
| `messageId`, `idempotencyKey` | `string` |
|
|
17
|
+
| `template`, `data` | template plugin fields |
|
|
18
|
+
| `react` | React plugin field |
|
|
19
|
+
|
|
20
|
+
`AddressInput` accepts a string, `{ address, name? }`, or an array of either.
|
|
21
|
+
|
|
22
|
+
<Cards><Card title="Attachments" href="/docs/guides/attachments" /></Cards>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Push options
|
|
3
|
+
description: Fields accepted by `PushSender.send`.
|
|
4
|
+
icon: Bell
|
|
5
|
+
source: "src/core/push-types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
| Field | Type | Required |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `subscription` | `PushSubscription` | yes |
|
|
11
|
+
| `title` | `string` | yes |
|
|
12
|
+
| `body` | `string` | yes |
|
|
13
|
+
| `data` | `Record<string, unknown>` | no |
|
|
14
|
+
| `icon` | `string` | no |
|
|
15
|
+
| `ttl` | `number` | no |
|
|
16
|
+
| `messageId` | `string` | no |
|
|
17
|
+
|
|
18
|
+
A subscription contains `endpoint`, `keys.p256dh`, and `keys.auth`.
|
|
19
|
+
|
|
20
|
+
<Cards><Card title="Push channel" href="/docs/channels/push" /></Cards>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SMS options
|
|
3
|
+
description: Fields accepted by `SmsSender.send`.
|
|
4
|
+
icon: MessageSquare
|
|
5
|
+
source: "src/core/sms-types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
| Field | Type | Required |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `to` | `string` | yes |
|
|
11
|
+
| `body` | `string` | yes |
|
|
12
|
+
| `from` | `string` | no |
|
|
13
|
+
| `messageId` | `string` | no |
|
|
14
|
+
|
|
15
|
+
The result includes `messageId`, `to`, `status`, `response`, and optional `provider`.
|
|
16
|
+
|
|
17
|
+
<Cards><Card title="SMS channel" href="/docs/channels/sms" /></Cards>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Transport contracts
|
|
3
|
+
description: Implement a channel transport to integrate a provider not shipped by sently.
|
|
4
|
+
icon: FileType
|
|
5
|
+
source: "src/core/types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
| Contract | Required method | Optional methods |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `Transport` | `send(MailOptions)` | `sendBatch`, `verify`, `close` |
|
|
11
|
+
| `SmsTransport` | `send(SmsOptions)` | `verify`, `close` |
|
|
12
|
+
| `WhatsAppTransport` | `send(WhatsAppOptions)` | `verify`, `close` |
|
|
13
|
+
| `PushTransport` | `send(PushOptions)` | `verify`, `close` |
|
|
14
|
+
|
|
15
|
+
Each contract can expose a stable optional `provider` identifier for hooks and observability.
|
|
16
|
+
|
|
17
|
+
<Cards><Card title="Channels" href="/docs/channels" /></Cards>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Webhook events
|
|
3
|
+
description: Normalized EmailEvent fields returned by every provider webhook parser.
|
|
4
|
+
icon: Webhook
|
|
5
|
+
source: "src/webhooks/types.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Every parser returns `EmailEvent[]` so delivery webhooks can share one application pipeline.
|
|
9
|
+
Verify signatures in the [webhooks guide](/docs/guides/webhooks) before you process events.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Branch on `event.type` and keep provider-specific details in `event.raw` when you need them.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## EmailEvent
|
|
16
|
+
|
|
17
|
+
| Field | Type | Meaning |
|
|
18
|
+
| ----- | ---- | ------- |
|
|
19
|
+
| `provider` | `string` | Parser id (e.g. `"sndr"`, `"resend"`). |
|
|
20
|
+
| `type` | union below | Normalized lifecycle type. |
|
|
21
|
+
| `messageId` | `string?` | Provider message id when present. |
|
|
22
|
+
| `recipient` | `string?` | Primary recipient when present. |
|
|
23
|
+
| `timestamp` | `Date?` | Event time when present. |
|
|
24
|
+
| `raw` | `unknown` | Original provider payload. |
|
|
25
|
+
|
|
26
|
+
## Normalized `type` values
|
|
27
|
+
|
|
28
|
+
| Value | Meaning |
|
|
29
|
+
| ----- | ------- |
|
|
30
|
+
| `delivered` | Accepted / delivered to the mailbox provider. |
|
|
31
|
+
| `bounced` | Hard failure or bounce. |
|
|
32
|
+
| `complained` | Spam complaint. |
|
|
33
|
+
| `opened` | Open tracking. |
|
|
34
|
+
| `clicked` | Click tracking. |
|
|
35
|
+
| `deferred` | Temporary deferral. |
|
|
36
|
+
| `unknown` | Unmapped or informational provider event. |
|
|
37
|
+
|
|
38
|
+
## SNDR mapping
|
|
39
|
+
|
|
40
|
+
| SNDR `type` | Normalized `type` |
|
|
41
|
+
| ----------- | ----------------- |
|
|
42
|
+
| `email.delivered` | `delivered` |
|
|
43
|
+
| `email.bounced` | `bounced` |
|
|
44
|
+
| `email.unsubscribed` | `unknown` |
|
|
45
|
+
|
|
46
|
+
SNDR `data.email_id` becomes `messageId`. Recipients come from `data.to` or `data.addresses`.
|
|
47
|
+
|
|
48
|
+
## Troubleshooting
|
|
49
|
+
|
|
50
|
+
<Accordions>
|
|
51
|
+
|
|
52
|
+
<Accordion title="Why is unsubscribed mapped to unknown?">
|
|
53
|
+
|
|
54
|
+
The shared `EmailEvent.type` union has no unsubscribe value. Keep the vendor payload in `raw` if you need unsubscribe-specific handling.
|
|
55
|
+
|
|
56
|
+
</Accordion>
|
|
57
|
+
|
|
58
|
+
</Accordions>
|
|
59
|
+
|
|
60
|
+
## Learn more
|
|
61
|
+
|
|
62
|
+
- [Webhooks guide](/docs/guides/webhooks) — verify + parse flow
|
|
63
|
+
- [SNDR transport](/docs/transports/sndr) — sending side
|
|
64
|
+
|
|
65
|
+
## Next
|
|
66
|
+
|
|
67
|
+
<Cards>
|
|
68
|
+
<Card title="Webhooks" description="Parse and verify provider payloads." href="/docs/guides/webhooks" />
|
|
69
|
+
<Card title="Exports" description="Package entrypoints." href="/docs/reference/exports" />
|
|
70
|
+
</Cards>
|