@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 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
- | npm package | [npmjs.com/package/@zindua/sdk](https://www.npmjs.com/package/@zindua/sdk) |
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
- | Domain & custom sender | [zindua.run/developers#domain](https://zindua.run/developers#domain) |
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
- | **Pro** or **Team** plan | Email API on production sends (Free = WhatsApp OTP API) | Dashboard → **Billing** |
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
- // { success: true, status: "queued", logId: "…", channel: "email" | "whatsapp", … }
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.1";
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
- return new errors_1.ZinduaError(message, {
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 ? details : undefined,
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zindua/sdk",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Official Zindua SDK for Node.js — transactional email and WhatsApp via POST /api/v1/send.",
5
5
  "author": "Zindua",
6
6
  "license": "MIT",