@zindua/sdk 1.2.1 → 1.2.3
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/README.md +85 -15
- package/dist/client.d.ts +30 -0
- package/dist/client.js +20 -3
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
Official **server-side** SDK for [Zindua](https://zindua.run): one API for transactional **email** and **WhatsApp** (`POST /api/v1/send`).
|
|
4
4
|
|
|
5
|
-
**npm:** [https://www.npmjs.com/package/@zindua/sdk](https://www.npmjs.com/package/@zindua/sdk) (this URL does not change when we ship new versions)
|
|
6
|
-
|
|
7
5
|
| Resource | Link |
|
|
8
6
|
|----------|------|
|
|
9
|
-
|
|
|
10
|
-
| Website | [zindua.run](https://zindua.run) |
|
|
11
|
-
| Developer documentation | [zindua.run/developers](https://zindua.run/developers) |
|
|
7
|
+
| Website & docs | [zindua.run/developers](https://zindua.run/developers) |
|
|
12
8
|
| HTTP / cURL (no SDK) | [zindua.run/developers#http](https://zindua.run/developers#http) |
|
|
13
|
-
|
|
|
9
|
+
| Pricing & plans | [zindua.run/pricing](https://zindua.run/pricing) |
|
|
14
10
|
| Dashboard | [zindua.run/login](https://zindua.run/login) |
|
|
15
11
|
|
|
12
|
+
Install: `npm install @zindua/sdk`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Templates and languages
|
|
17
|
+
|
|
18
|
+
Create templates in the **dashboard** (one **slug** per template, e.g. `welcome`, `otp-verification`). Each template can include **email HTML** and **WhatsApp text** for the same slug.
|
|
19
|
+
|
|
20
|
+
Send in a locale with the `lang` field (ISO code: `fr`, `en`, `sw`, …). If you omit `lang`, Zindua uses your project default.
|
|
21
|
+
|
|
22
|
+
| Plan | Templates per project | Languages per template (email + WhatsApp) |
|
|
23
|
+
|------|----------------------|-------------------------------------------|
|
|
24
|
+
| **Free** | 5 | 3 |
|
|
25
|
+
| **Pro** | 50 | 10 |
|
|
26
|
+
| **Team** | 1000 | 100 |
|
|
27
|
+
|
|
28
|
+
On **Free**, **email API** works when you connect a **Service** (Gmail, SMTP, etc.) on the project. **WhatsApp OTP** works on Free (quota applies). **Pro/Team** raise template and volume limits. See [pricing](https://zindua.run/pricing).
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// French version of the same template slug
|
|
32
|
+
await zindua.send({
|
|
33
|
+
to: "user@example.com",
|
|
34
|
+
template: "welcome",
|
|
35
|
+
lang: "fr",
|
|
36
|
+
variables: { name: "Alex" },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// WhatsApp: same slug, different channel + E.164 phone
|
|
40
|
+
await zindua.send({
|
|
41
|
+
to: "+243812345678",
|
|
42
|
+
channel: "whatsapp",
|
|
43
|
+
template: "otp-verification",
|
|
44
|
+
lang: "fr",
|
|
45
|
+
variables: { code: "4592", app: "MonApp" },
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
16
49
|
---
|
|
17
50
|
|
|
18
51
|
## Requirements
|
|
@@ -26,7 +59,7 @@ Before using the SDK, set up your Zindua project in the dashboard:
|
|
|
26
59
|
| Email **Service** connected (Gmail, Outlook, SendGrid, SMTP, …) | `channel: "email"` | Dashboard → **Service** |
|
|
27
60
|
| WhatsApp connected (QR) | `channel: "whatsapp"` | Dashboard → **WhatsApp** |
|
|
28
61
|
| Template slug (e.g. `welcome`, `otp-verification`) | All | Dashboard → **Templates** |
|
|
29
|
-
| **
|
|
62
|
+
| **Service** on Free or **Pro/Team** plan | Email via API when Service is connected (Free) or full quotas (paid) | Dashboard → **Service** / **Billing** |
|
|
30
63
|
|
|
31
64
|
**Runtime**
|
|
32
65
|
|
|
@@ -133,9 +166,51 @@ await zindua.send({
|
|
|
133
166
|
|
|
134
167
|
```typescript
|
|
135
168
|
const result = await zindua.send({ /* … */ });
|
|
136
|
-
// {
|
|
169
|
+
// {
|
|
170
|
+
// success: true,
|
|
171
|
+
// status: "queued",
|
|
172
|
+
// logId: "…",
|
|
173
|
+
// channel: "email",
|
|
174
|
+
// context: { project, apiKey: { mode, suffix }, plan, channels }
|
|
175
|
+
// }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The API never returns your full API key. `context.apiKey.suffix` is the last 4 characters (match in Dashboard).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Several clients (multi-project)
|
|
183
|
+
|
|
184
|
+
**Recommended:** one Zindua project per client → one `znd_live_…` per project.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# .env — server only
|
|
188
|
+
ZINDUA_KEY_MELLIA=znd_live_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
189
|
+
ZINDUA_KEY_OVERLOOK=znd_live_yyyyyyyyyyyyyyyyyyyyyyyy
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { Zindua } from "@zindua/sdk";
|
|
194
|
+
|
|
195
|
+
const zinduaByTenant = {
|
|
196
|
+
mellia: new Zindua({ apiKey: process.env.ZINDUA_KEY_MELLIA! }),
|
|
197
|
+
overlook: new Zindua({ apiKey: process.env.ZINDUA_KEY_OVERLOOK! }),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
await zinduaByTenant[tenantId].send({
|
|
201
|
+
to: "user@example.com",
|
|
202
|
+
template: "otp",
|
|
203
|
+
variables: { code: "482910" },
|
|
204
|
+
});
|
|
137
205
|
```
|
|
138
206
|
|
|
207
|
+
| Approach | When to use |
|
|
208
|
+
|----------|-------------|
|
|
209
|
+
| **2+ Zindua projects** | Different clients, senders, templates, or quotas |
|
|
210
|
+
| **1 Zindua project** | Same sender and templates for everyone (one key) |
|
|
211
|
+
|
|
212
|
+
More detail: [zindua.run/developers#multi-project](https://zindua.run/developers#multi-project) and repo `config/API-SECURITY-MULTI-PROJECT-FR.md`.
|
|
213
|
+
|
|
139
214
|
---
|
|
140
215
|
|
|
141
216
|
## Errors
|
|
@@ -161,10 +236,12 @@ try {
|
|
|
161
236
|
## Security
|
|
162
237
|
|
|
163
238
|
- **Server only** — throws in browser environments (`BROWSER_FORBIDDEN`).
|
|
239
|
+
- **Bearer header only** — `Authorization: Bearer znd_live_…`. Keys in URL, JSON body, or `X-Api-Key` are rejected by the platform API.
|
|
164
240
|
- **HTTPS** for `baseUrl` except `http://localhost` in development.
|
|
165
241
|
- **No credentials in URL** — rejects `baseUrl` with embedded username/password.
|
|
166
242
|
- **Input validation** — E.164 phones, email format, template slug, variable size limits (aligned with the platform API).
|
|
167
243
|
- **Redirects disabled** — `fetch` uses `redirect: "error"`.
|
|
244
|
+
- **CORS** — optional origin allowlist in Dashboard → Settings → Integrations (browser calls only).
|
|
168
245
|
|
|
169
246
|
---
|
|
170
247
|
|
|
@@ -202,13 +279,6 @@ Full reference: [HTTP / cURL](https://zindua.run/developers#http).
|
|
|
202
279
|
|
|
203
280
|
---
|
|
204
281
|
|
|
205
|
-
## Releases and versioning
|
|
206
|
-
|
|
207
|
-
- The registry page [npmjs.com/package/@zindua/sdk](https://www.npmjs.com/package/@zindua/sdk) is **permanent**.
|
|
208
|
-
- Each release only updates the **version** (`1.2.0`, `1.2.1`, `1.3.0`, …) using [semver](https://semver.org/).
|
|
209
|
-
- Consumers pin in `package.json`: `"@zindua/sdk": "^1.2.0"`, or run `npm update @zindua/sdk` for the latest compatible version.
|
|
210
|
-
- Maintainers: bump `version` in `package.json`, sync `SDK_VERSION` in `src/client.ts`, then `npm publish --access public` from this folder (see `PUBLISHING.md`).
|
|
211
|
-
|
|
212
282
|
## Local development (monorepo)
|
|
213
283
|
|
|
214
284
|
```bash
|
package/dist/client.d.ts
CHANGED
|
@@ -17,6 +17,34 @@ export type ZinduaClientOptions = {
|
|
|
17
17
|
/** Request timeout (default 30s, max 120s). */
|
|
18
18
|
timeoutMs?: number;
|
|
19
19
|
};
|
|
20
|
+
export type ZinduaSendContext = {
|
|
21
|
+
project: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
teamId: string;
|
|
26
|
+
};
|
|
27
|
+
apiKey: {
|
|
28
|
+
mode: "live" | "test";
|
|
29
|
+
prefix: string;
|
|
30
|
+
suffix: string;
|
|
31
|
+
};
|
|
32
|
+
plan: {
|
|
33
|
+
slug: string;
|
|
34
|
+
name: string;
|
|
35
|
+
status: string;
|
|
36
|
+
emailApiEnabled: boolean;
|
|
37
|
+
whatsappEnabled: boolean;
|
|
38
|
+
emailQuota: number;
|
|
39
|
+
emailsUsed: number;
|
|
40
|
+
whatsappOtpQuota: number | null;
|
|
41
|
+
whatsappUsed: number;
|
|
42
|
+
} | null;
|
|
43
|
+
channels: {
|
|
44
|
+
email: boolean;
|
|
45
|
+
whatsapp: boolean;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
20
48
|
export type ZinduaSendResult = {
|
|
21
49
|
success: true;
|
|
22
50
|
channel: SendChannel;
|
|
@@ -25,6 +53,8 @@ export type ZinduaSendResult = {
|
|
|
25
53
|
langUsed?: string;
|
|
26
54
|
langFallback?: boolean;
|
|
27
55
|
testMode?: boolean;
|
|
56
|
+
project?: string;
|
|
57
|
+
context?: ZinduaSendContext;
|
|
28
58
|
};
|
|
29
59
|
export declare class Zindua {
|
|
30
60
|
private readonly apiKey;
|
package/dist/client.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Zindua = void 0;
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
5
|
const validate_1 = require("./validate");
|
|
6
|
-
const SDK_VERSION = "1.2.
|
|
6
|
+
const SDK_VERSION = "1.2.3";
|
|
7
7
|
const USER_AGENT = `Zindua-JS/${SDK_VERSION}`;
|
|
8
8
|
function buildPayload(options, channel) {
|
|
9
9
|
const to = (0, validate_1.validateRecipient)(options.to, channel);
|
|
@@ -46,12 +46,25 @@ function parseApiError(status, body) {
|
|
|
46
46
|
const details = {};
|
|
47
47
|
if (typeof body.code === "string")
|
|
48
48
|
details.code = body.code;
|
|
49
|
+
if (typeof body.hint === "string")
|
|
50
|
+
details.hint = body.hint;
|
|
49
51
|
if (typeof body.retryAfterSec === "number")
|
|
50
52
|
details.retryAfterSec = body.retryAfterSec;
|
|
51
|
-
|
|
53
|
+
if (Array.isArray(body.availableTemplateSlugs)) {
|
|
54
|
+
details.availableTemplateSlugs = body.availableTemplateSlugs;
|
|
55
|
+
}
|
|
56
|
+
if (body.context && typeof body.context === "object") {
|
|
57
|
+
details.context = body.context;
|
|
58
|
+
}
|
|
59
|
+
const fullMessage = typeof body.hint === "string" && body.hint.length > 0 ? `${message} ${body.hint}` : message;
|
|
60
|
+
return new errors_1.ZinduaError(fullMessage, {
|
|
52
61
|
status,
|
|
53
62
|
code: typeof body.code === "string" ? body.code : "API_ERROR",
|
|
54
|
-
details: Object.keys(details).length > 0
|
|
63
|
+
details: Object.keys(details).length > 0
|
|
64
|
+
? details
|
|
65
|
+
: typeof body.hint === "string"
|
|
66
|
+
? { hint: body.hint }
|
|
67
|
+
: undefined,
|
|
55
68
|
});
|
|
56
69
|
}
|
|
57
70
|
class Zindua {
|
|
@@ -115,6 +128,10 @@ class Zindua {
|
|
|
115
128
|
langUsed: typeof data.langUsed === "string" ? data.langUsed : undefined,
|
|
116
129
|
langFallback: typeof data.langFallback === "boolean" ? data.langFallback : undefined,
|
|
117
130
|
testMode: typeof data.testMode === "boolean" ? data.testMode : undefined,
|
|
131
|
+
project: typeof data.project === "string" ? data.project : undefined,
|
|
132
|
+
context: data.context && typeof data.context === "object"
|
|
133
|
+
? data.context
|
|
134
|
+
: undefined,
|
|
118
135
|
};
|
|
119
136
|
}
|
|
120
137
|
catch (err) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Zindua } from "./client";
|
|
2
|
-
export type { ZinduaClientOptions, ZinduaSendOptions, ZinduaSendResult, SendChannel, } from "./client";
|
|
2
|
+
export type { ZinduaClientOptions, ZinduaSendContext, ZinduaSendOptions, ZinduaSendResult, SendChannel, } from "./client";
|
|
3
3
|
export { ZinduaError } from "./errors";
|
|
4
4
|
export type { ZinduaErrorCode } from "./errors";
|
|
5
5
|
export { DEFAULT_API_BASE, LIMITS } from "./validate";
|