sently 1.0.0 → 1.1.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 +3 -2
- package/CHANGELOG.md +97 -0
- package/README.md +20 -5
- package/dist/chunk-z1589fjk.js.map +2 -2
- package/dist/core/push-types.d.ts +53 -4
- package/dist/transports/inbucket.d.ts +196 -0
- package/dist/transports/inbucket.js +3 -0
- package/dist/transports/inbucket.js.map +10 -0
- package/dist/transports/mailpit.d.ts +108 -8
- package/dist/transports/mailpit.js +2 -2
- package/dist/transports/mailpit.js.map +3 -3
- package/dist/transports/taqnyat-sms.d.ts +85 -0
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +3 -3
- package/dist/transports/taqnyat-whatsapp.d.ts +112 -4
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +3 -3
- package/dist/transports/webpush.d.ts +24 -1
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +3 -3
- package/package.json +7 -2
- package/site/content/docs/ai/llms-txt.mdx +2 -0
- package/site/content/docs/channels/email.mdx +2 -1
- package/site/content/docs/channels/push.mdx +3 -1
- package/site/content/docs/decorators/preview.mdx +2 -1
- package/site/content/docs/get-started/entrypoints.mdx +1 -1
- package/site/content/docs/get-started/support-matrix.mdx +2 -2
- package/site/content/docs/guides/vendor-extras-otp.mdx +2 -1
- package/site/content/docs/guides/webhooks.mdx +2 -0
- package/site/content/docs/guides/webpush-interop.mdx +9 -1
- package/site/content/docs/reference/exports.mdx +1 -1
- package/site/content/docs/reference/push-options.mdx +22 -5
- package/site/content/docs/transports/inbucket.mdx +200 -0
- package/site/content/docs/transports/index.mdx +2 -3
- package/site/content/docs/transports/mailpit.mdx +114 -14
- package/site/content/docs/transports/meta.json +5 -4
- package/site/content/docs/transports/smtp.mdx +3 -2
- package/site/content/docs/transports/sndr.mdx +68 -5
- package/site/content/docs/transports/taqnyat.mdx +361 -0
- package/site/content/docs/transports/webpush.mdx +208 -17
- package/site/content/docs/transports/taqnyat-mail.mdx +0 -41
- package/site/content/docs/transports/taqnyat-sms.mdx +0 -41
- package/site/content/docs/transports/taqnyat-whatsapp.mdx +0 -40
|
@@ -5,8 +5,12 @@ icon: Truck
|
|
|
5
5
|
source: "src/transports/sndr.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Use SNDR
|
|
9
|
-
Wire `SndrTransport` into `createMailer
|
|
8
|
+
Use SNDR for app-triggered email — receipts, sign-in mail, and other transactional sends — over HTTPS JSON.
|
|
9
|
+
Wire `SndrTransport` into `createMailer`; do not call `@rkiza/sndr` from application code.
|
|
10
|
+
|
|
11
|
+
<LiveVerified>
|
|
12
|
+
Email send against SNDR’s production API succeeded in sently’s opt-in live suite (verified domain + real recipient).
|
|
13
|
+
</LiveVerified>
|
|
10
14
|
|
|
11
15
|
<Callout title="The one rule">
|
|
12
16
|
Import from `sently/transports/sndr`, pass the transport to `await createMailer(...)`, and send
|
|
@@ -55,7 +59,7 @@ console.log(result.messageId); // e.g. em_…
|
|
|
55
59
|
console.log(result.response); // e.g. queued
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
`from` must use a domain verified in the SNDR dashboard.
|
|
62
|
+
`from` must use a domain verified in the [SNDR dashboard](https://www.sndr.sh/) (SPF, DKIM, DMARC).
|
|
59
63
|
|
|
60
64
|
</Step>
|
|
61
65
|
|
|
@@ -82,10 +86,12 @@ console.log(result.response); // e.g. queued
|
|
|
82
86
|
| `idempotencyKey` / `messageId` | `Idempotency-Key` | Sent when present. |
|
|
83
87
|
| `data` | `variables` | Only when a template id is set. |
|
|
84
88
|
|
|
89
|
+
`POST /v1/send` is idempotent when `Idempotency-Key` is supplied.
|
|
90
|
+
|
|
85
91
|
## Templates
|
|
86
92
|
|
|
87
93
|
Set a template with the `x-sndr-template-id` header (or `defaultTemplateId` on the transport).
|
|
88
|
-
Pass template variables through `data
|
|
94
|
+
Pass template variables through `data` (`{{ variable }}` placeholders on SNDR).
|
|
89
95
|
|
|
90
96
|
```ts
|
|
91
97
|
import { SNDR_TEMPLATE_ID_HEADER } from "sently/transports/sndr";
|
|
@@ -105,13 +111,57 @@ await mailer.send({
|
|
|
105
111
|
|
|
106
112
|
Failed HTTP responses throw `SndrError` (`SentlyError`) with the API `error.message` when present.
|
|
107
113
|
|
|
114
|
+
| SNDR `error.code` (common) | Meaning |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `invalid_request` | Malformed payload |
|
|
117
|
+
| `unauthenticated` | Missing or invalid API key |
|
|
118
|
+
| `rate_limited` | Slow down |
|
|
119
|
+
| `domain_not_verified` | Verify the sending domain first |
|
|
120
|
+
| `recipient_suppressed` | Address is on the suppression list |
|
|
121
|
+
|
|
122
|
+
## Webhooks
|
|
123
|
+
|
|
124
|
+
Import from `sently/webhooks/sndr`. Verify `X-Sndr-Signature` (`t=…,v1=…`) over the **raw** body, then `parse`.
|
|
125
|
+
|
|
126
|
+
| SNDR event | Normalized `EmailEvent.type` |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `email.queued` | `deferred` |
|
|
129
|
+
| `email.delivered` | `delivered` |
|
|
130
|
+
| `email.bounced` | `bounced` |
|
|
131
|
+
| `email.failed` | `unknown` |
|
|
132
|
+
| `email.complained` | `complained` |
|
|
133
|
+
| `email.opened` | `opened` |
|
|
134
|
+
| `email.clicked` | `clicked` |
|
|
135
|
+
| `email.unsubscribed` | `unknown` |
|
|
136
|
+
|
|
137
|
+
See [Webhooks](/docs/guides/webhooks#sndr-signatures) for the HMAC details.
|
|
138
|
+
|
|
139
|
+
## What sently covers vs SNDR platform
|
|
140
|
+
|
|
141
|
+
| Surface | In sently | Notes |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| Send (`POST /v1/send`) | Yes — `SndrTransport` | Channel: `createMailer` |
|
|
144
|
+
| Templates + variables | Yes | Header / `defaultTemplateId` + `data` |
|
|
145
|
+
| Idempotency | Yes | `idempotencyKey` / `messageId` |
|
|
146
|
+
| Domain verify | Yes — `verify()` | `GET /v1/domains` |
|
|
147
|
+
| Delivery webhooks | Yes — `sently/webhooks/sndr` | Signed parse |
|
|
148
|
+
| Contacts / contact groups / broadcasts | No | SNDR dashboard / their API |
|
|
149
|
+
| Analytics / suppressions admin | No | Use SNDR dashboard |
|
|
150
|
+
| Attachments on HTTP send | Not mapped | Prefer SMTP or ask SNDR if their send API gains attachments |
|
|
151
|
+
|
|
108
152
|
## Troubleshooting
|
|
109
153
|
|
|
110
154
|
<Accordions>
|
|
111
155
|
|
|
112
156
|
<Accordion title="from domain is not verified">
|
|
113
157
|
|
|
114
|
-
SNDR rejects sends from unverified domains.
|
|
158
|
+
SNDR rejects sends from unverified domains. Add SPF, DKIM, and DMARC in the dashboard Domains page, click Verify, then retry with that domain in `from`.
|
|
159
|
+
|
|
160
|
+
</Accordion>
|
|
161
|
+
|
|
162
|
+
<Accordion title="recipient_suppressed">
|
|
163
|
+
|
|
164
|
+
The address is on SNDR’s suppression list (bounce/complaint). Remove it only if the recipient opted back in; otherwise pick another address.
|
|
115
165
|
|
|
116
166
|
</Accordion>
|
|
117
167
|
|
|
@@ -129,6 +179,19 @@ No. Use `createMailer` plus `SndrTransport`. Keep vendor extras off the shared e
|
|
|
129
179
|
|
|
130
180
|
</Accordions>
|
|
131
181
|
|
|
182
|
+
## Contact & resources
|
|
183
|
+
|
|
184
|
+
| Contact | Detail |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| Direct email | [sndr@rkiza.sa](mailto:sndr@rkiza.sa) |
|
|
187
|
+
| Contact form | [sndr.sh/contact](https://www.sndr.sh/contact) |
|
|
188
|
+
| Docs | [sndr.sh/docs](https://www.sndr.sh/docs) |
|
|
189
|
+
| API reference | [API Reference](https://www.sndr.sh/docs/api-reference) |
|
|
190
|
+
| Status | [sndr.sh/status](https://www.sndr.sh/status) |
|
|
191
|
+
| X / Twitter | [@usesndr](https://x.com/usesndr) |
|
|
192
|
+
| LinkedIn | [SNDR company](https://www.linkedin.com/company/104143949) |
|
|
193
|
+
| GitHub | [github.com/rkiza/sndr](https://github.com/rkiza/sndr) |
|
|
194
|
+
|
|
132
195
|
## Learn more
|
|
133
196
|
|
|
134
197
|
- [Email channel](/docs/channels/email) — mailer options and send pipeline
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Taqnyat
|
|
3
|
+
description: Send email, SMS, and WhatsApp through Taqnyat — one provider, three channel transports.
|
|
4
|
+
icon: Truck
|
|
5
|
+
source: "src/transports/taqnyat-sms.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Taqnyat is a multi-channel provider. Wire each product into its sently channel sender — do not build a mega Taqnyat client.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">
|
|
11
|
+
Use `createMailer`, `createSmsSender`, or `createWhatsAppSender` with the matching `sently/transports/taqnyat-*` transport.
|
|
12
|
+
Vendor extras stay on the concrete transport instance — never on the shared channel sender.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
| Channel | Transport | Import |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| SMS | `TaqnyatSmsTransport` | `sently/transports/taqnyat-sms` |
|
|
18
|
+
| WhatsApp | `TaqnyatWhatsAppTransport` | `sently/transports/taqnyat-whatsapp` |
|
|
19
|
+
| Email | `TaqnyatMailTransport` | `sently/transports/taqnyat-mail` |
|
|
20
|
+
|
|
21
|
+
## SMS
|
|
22
|
+
|
|
23
|
+
<LiveVerified>
|
|
24
|
+
SMS send against Taqnyat’s production API succeeded in sently’s opt-in live suite (trial sender + Saudi destination).
|
|
25
|
+
</LiveVerified>
|
|
26
|
+
|
|
27
|
+
| Option | Type | Default or requirement |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `bearerToken` | `string` | required |
|
|
30
|
+
| `sender` | `string` | required — active portal sender name (case-sensitive) |
|
|
31
|
+
|
|
32
|
+
Recipients are normalized to international digits (no `+` / leading `00`).
|
|
33
|
+
|
|
34
|
+
### Setup
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createSmsSender } from "sently/sms";
|
|
38
|
+
import { TaqnyatSmsTransport } from "sently/transports/taqnyat-sms";
|
|
39
|
+
|
|
40
|
+
const taqnyat = new TaqnyatSmsTransport({
|
|
41
|
+
bearerToken: process.env.TAQNYAT_TOKEN!,
|
|
42
|
+
sender: "Taqnyat.sa",
|
|
43
|
+
});
|
|
44
|
+
const sms = createSmsSender({ transport: taqnyat });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
Pick a branch. Channel send goes through `sms`; everything else is called on `taqnyat`.
|
|
50
|
+
|
|
51
|
+
<Tabs items={["Send", "OTP", "Balance", "Senders", "Schedule"]}>
|
|
52
|
+
<Tab value="Send">
|
|
53
|
+
|
|
54
|
+
Immediate SMS via the channel sender.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const result = await sms.send({
|
|
58
|
+
to: "+9665xxxxxxxx",
|
|
59
|
+
body: "Hello from sently",
|
|
60
|
+
});
|
|
61
|
+
console.log(result.messageId, result.response);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Optional `from` overrides the transport `sender` for that send.
|
|
65
|
+
|
|
66
|
+
</Tab>
|
|
67
|
+
<Tab value="OTP">
|
|
68
|
+
|
|
69
|
+
Taqnyat Verify API on the transport (`sendOtp` → user enters code → `verifyOtp`).
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
await taqnyat.sendOtp({
|
|
73
|
+
to: "+9665xxxxxxxx",
|
|
74
|
+
requestId: "login-1",
|
|
75
|
+
lang: "en", // or "ar" (default)
|
|
76
|
+
note: "Acme login", // optional
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await taqnyat.verifyOtp({
|
|
80
|
+
to: "+9665xxxxxxxx",
|
|
81
|
+
requestId: "login-1",
|
|
82
|
+
code: "6240",
|
|
83
|
+
lang: "en",
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Success send code is `5`. Verify accepts `10`, `13`, or `19`.
|
|
88
|
+
|
|
89
|
+
</Tab>
|
|
90
|
+
<Tab value="Balance">
|
|
91
|
+
|
|
92
|
+
Account balance for preflight and ops (`GET /account/balance`).
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const balance = await taqnyat.getBalance();
|
|
96
|
+
// { accountStatus, balance, currency, accountExpiryDate?, provider }
|
|
97
|
+
console.log(balance.balance, balance.currency);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
</Tab>
|
|
101
|
+
<Tab value="Senders">
|
|
102
|
+
|
|
103
|
+
List registered sender names before you hard-code `sender`.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const senders = await taqnyat.listSenders();
|
|
107
|
+
// [{ senderName: "Taqnyat.sa", status: "active" }, ...]
|
|
108
|
+
const active = senders.filter((s) => s.status === "active");
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Trial accounts may return an empty list while the portal still allows `Taqnyat.sa`.
|
|
112
|
+
|
|
113
|
+
</Tab>
|
|
114
|
+
<Tab value="Schedule">
|
|
115
|
+
|
|
116
|
+
Schedule a send, then delete it with the same `deleteId` if you need to cancel.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
await taqnyat.schedule({
|
|
120
|
+
to: "+9665xxxxxxxx",
|
|
121
|
+
body: "Later",
|
|
122
|
+
scheduledDatetime: "2030-01-01T10:00", // Taqnyat format
|
|
123
|
+
deleteId: "demo-1",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await taqnyat.deleteScheduled("demo-1");
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Useful for dry-runs: schedule → confirm in portal → delete to reclaim cost when the account allows it.
|
|
130
|
+
|
|
131
|
+
</Tab>
|
|
132
|
+
</Tabs>
|
|
133
|
+
|
|
134
|
+
## WhatsApp
|
|
135
|
+
|
|
136
|
+
<LiveVerified>
|
|
137
|
+
Template send against Taqnyat’s WhatsApp API succeeded in sently’s opt-in live suite (approved sandbox template + authorized number).
|
|
138
|
+
</LiveVerified>
|
|
139
|
+
|
|
140
|
+
| Option | Type | Default or requirement |
|
|
141
|
+
| --- | --- | --- |
|
|
142
|
+
| `bearerToken` | `string` | required |
|
|
143
|
+
|
|
144
|
+
Business-initiated chats must start with an **approved** template. Session `text` is only allowed after the user replies (24h window).
|
|
145
|
+
Queued accepts may return `statuses: "PENDING"` with no `message_id` yet — still treated as accepted.
|
|
146
|
+
|
|
147
|
+
### Setup
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { createWhatsAppSender } from "sently/whatsapp";
|
|
151
|
+
import { TaqnyatWhatsAppTransport } from "sently/transports/taqnyat-whatsapp";
|
|
152
|
+
|
|
153
|
+
const transport = new TaqnyatWhatsAppTransport({
|
|
154
|
+
bearerToken: process.env.TAQNYAT_WHATSAPP_TOKEN!,
|
|
155
|
+
});
|
|
156
|
+
const wa = createWhatsAppSender({ transport });
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Features
|
|
160
|
+
|
|
161
|
+
Pick a branch. Template/session send goes through `wa`; extras stay on `transport`.
|
|
162
|
+
|
|
163
|
+
<Tabs items={["Template", "Session text", "Opt-in", "Templates", "Failover"]}>
|
|
164
|
+
<Tab value="Template">
|
|
165
|
+
|
|
166
|
+
Business-initiated message with an approved template.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
await transport.optIn("+9665xxxxxxxx"); // once per recipient when required
|
|
170
|
+
|
|
171
|
+
const result = await wa.send({
|
|
172
|
+
to: "+9665xxxxxxxx",
|
|
173
|
+
template: {
|
|
174
|
+
name: "demotest1_testr11",
|
|
175
|
+
language: "ar",
|
|
176
|
+
// components: [...] // when the template has variables
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
console.log(result.messageId, result.response);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Sandbox: recipient must be on the authorized numbers list.
|
|
183
|
+
|
|
184
|
+
</Tab>
|
|
185
|
+
<Tab value="Session text">
|
|
186
|
+
|
|
187
|
+
Free-form text only inside an open 24h customer-care session (after the user replies).
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
await wa.send({
|
|
191
|
+
to: "+9665xxxxxxxx",
|
|
192
|
+
text: "Thanks — how can we help?",
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Do not use this as the first business-initiated message.
|
|
197
|
+
|
|
198
|
+
</Tab>
|
|
199
|
+
<Tab value="Opt-in">
|
|
200
|
+
|
|
201
|
+
Record consent before template campaigns (`POST` / `DELETE` provision opt-in).
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
await transport.optIn("+9665xxxxxxxx");
|
|
205
|
+
// or several:
|
|
206
|
+
await transport.optIn(["+9665xxxxxxxx", "9665yyyyyyyy"]);
|
|
207
|
+
|
|
208
|
+
await transport.optOut("+9665xxxxxxxx");
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Numbers are normalized the same way as send (`+` / `00` stripped).
|
|
212
|
+
|
|
213
|
+
</Tab>
|
|
214
|
+
<Tab value="Templates">
|
|
215
|
+
|
|
216
|
+
List, create, and delete WhatsApp templates on the transport.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const templates = await transport.listTemplates();
|
|
220
|
+
const approved = templates.filter((t) => t.status === "approved");
|
|
221
|
+
|
|
222
|
+
const created = await transport.createTemplate({
|
|
223
|
+
name: "sently_test",
|
|
224
|
+
language: "ar",
|
|
225
|
+
category: "UTILITY",
|
|
226
|
+
components: [{ type: "BODY", text: "Hello from sently" }],
|
|
227
|
+
});
|
|
228
|
+
// { id?, category?, status: "PENDING", provider }
|
|
229
|
+
|
|
230
|
+
await transport.deleteTemplate({
|
|
231
|
+
name: "sently_test",
|
|
232
|
+
id: created.id!,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Meta must approve a template before you can send it.
|
|
237
|
+
|
|
238
|
+
</Tab>
|
|
239
|
+
<Tab value="Failover">
|
|
240
|
+
|
|
241
|
+
Same WhatsApp send URL with nested SMS and/or email fallback branches.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
await transport.sendWithFailover(
|
|
245
|
+
{
|
|
246
|
+
to: "+9665xxxxxxxx",
|
|
247
|
+
template: { name: "welcome", language: "ar" },
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
sms: {
|
|
251
|
+
sender: "Taqnyat.sa",
|
|
252
|
+
campaign: "sently",
|
|
253
|
+
body: "SMS fallback",
|
|
254
|
+
},
|
|
255
|
+
mail: {
|
|
256
|
+
from: "hi@example.com",
|
|
257
|
+
to: "user@example.com",
|
|
258
|
+
campaign: "sently",
|
|
259
|
+
subject: "Fallback",
|
|
260
|
+
msg: "Email fallback",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Keep `campaign` aligned with email `campaignName` when both channels share a campaign.
|
|
267
|
+
|
|
268
|
+
</Tab>
|
|
269
|
+
</Tabs>
|
|
270
|
+
|
|
271
|
+
## Email
|
|
272
|
+
|
|
273
|
+
| Option | Type | Default or requirement |
|
|
274
|
+
| --- | --- | --- |
|
|
275
|
+
| `bearerToken` | `string` | required |
|
|
276
|
+
| `campaignName` | `string` | required |
|
|
277
|
+
|
|
278
|
+
`from` should be a sender address your Taqnyat account is allowed to use. See [Sender Approval](https://docs.taqnyat.sa/sender-approval) if email is not enabled in the portal yet.
|
|
279
|
+
|
|
280
|
+
### Features
|
|
281
|
+
|
|
282
|
+
<Tabs items={["Send"]}>
|
|
283
|
+
<Tab value="Send">
|
|
284
|
+
|
|
285
|
+
Transactional email via the channel mailer.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { createMailer } from "sently/mailer";
|
|
289
|
+
import { TaqnyatMailTransport } from "sently/transports/taqnyat-mail";
|
|
290
|
+
|
|
291
|
+
const mailer = await createMailer({
|
|
292
|
+
transport: new TaqnyatMailTransport({
|
|
293
|
+
bearerToken: process.env.TAQNYAT_MAIL_TOKEN!,
|
|
294
|
+
campaignName: "sently",
|
|
295
|
+
}),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await mailer.send({
|
|
299
|
+
from: "noreply@example.com",
|
|
300
|
+
to: "person@example.com",
|
|
301
|
+
subject: "Hello",
|
|
302
|
+
text: "Hi",
|
|
303
|
+
// html: "<p>Hi</p>",
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Body is `html` when set, otherwise `text` (`msg` on Taqnyat’s API).
|
|
308
|
+
|
|
309
|
+
</Tab>
|
|
310
|
+
</Tabs>
|
|
311
|
+
|
|
312
|
+
## Troubleshooting
|
|
313
|
+
|
|
314
|
+
<Accordions>
|
|
315
|
+
<Accordion title="SMS — Sender Name not active / not accepted">
|
|
316
|
+
Open the **Senders** branch and call `listSenders()`, or use an active portal sender exactly as shown (trial accounts often use `Taqnyat.sa`).
|
|
317
|
+
</Accordion>
|
|
318
|
+
<Accordion title="WhatsApp — sandbox rejects the number">
|
|
319
|
+
Add the destination under Manage WhatsApp → Sandbox, then use the **Opt-in** branch before business-initiated templates.
|
|
320
|
+
</Accordion>
|
|
321
|
+
<Accordion title="WhatsApp — template not approved / not found">
|
|
322
|
+
Use the **Templates** branch (`listTemplates`) and pick status **approved** — name + language must match exactly.
|
|
323
|
+
</Accordion>
|
|
324
|
+
<Accordion title="Email — Error 14 error From">
|
|
325
|
+
Taqnyat rejected the `from` address. Confirm email is enabled and the sender is approved, then retry with that address.
|
|
326
|
+
</Accordion>
|
|
327
|
+
<Accordion title="Error 102 IP not authorized">
|
|
328
|
+
Developers → Security Settings: authorize your public IP or turn off the IP allowlist.
|
|
329
|
+
</Accordion>
|
|
330
|
+
<Accordion title="Should I call the provider SDK?">
|
|
331
|
+
No. Use the matching sently channel sender; open a feature branch above for vendor extras on the transport.
|
|
332
|
+
</Accordion>
|
|
333
|
+
</Accordions>
|
|
334
|
+
|
|
335
|
+
## Contact & resources
|
|
336
|
+
|
|
337
|
+
Provider contact details from Taqnyat (account setup, packages, and platform guides).
|
|
338
|
+
|
|
339
|
+
| Contact | Detail |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| Unified number | [920015404](tel:920015404) |
|
|
342
|
+
| Support email | [support@taqnyat.sa](mailto:support@taqnyat.sa) |
|
|
343
|
+
| Point of contact | [a.ghaith@taqnyat.sa](mailto:a.ghaith@taqnyat.sa) |
|
|
344
|
+
| Packages & pricing | [Offers & packages](https://taqnyat.sa/en/offers/packages/) |
|
|
345
|
+
| Platform explanations | [Technical explanations](https://portal.taqnyat.sa/technical_explanations/en/index.html) |
|
|
346
|
+
|
|
347
|
+
API reference: [SMS](https://dev.taqnyat.sa/ar/doc/sms/), [WhatsApp](https://dev.taqnyat.sa/ar/doc/whatsapp/), [Verify](https://dev.taqnyat.sa/en/doc/verify/), [Mail](https://dev.taqnyat.sa/ar/doc/mail/).
|
|
348
|
+
|
|
349
|
+
## Learn more
|
|
350
|
+
|
|
351
|
+
- [Email channel](/docs/channels/email) — mailer options and send pipeline
|
|
352
|
+
- [Sms channel](/docs/channels/sms) — SMS sender options
|
|
353
|
+
- [Whatsapp channel](/docs/channels/whatsapp) — template and session text
|
|
354
|
+
- [Vendor OTP extras](/docs/guides/vendor-extras-otp) — OTP helpers on concrete transports
|
|
355
|
+
|
|
356
|
+
## Next
|
|
357
|
+
|
|
358
|
+
<Cards>
|
|
359
|
+
<Card title="Transports" href="/docs/transports" />
|
|
360
|
+
<Card title="Vendor OTP extras" href="/docs/guides/vendor-extras-otp" />
|
|
361
|
+
</Cards>
|