@zindua/sdk 1.2.2 → 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
@@ -25,7 +25,7 @@ Send in a locale with the `lang` field (ISO code: `fr`, `en`, `sw`, …). If you
25
25
  | **Pro** | 50 | 10 |
26
26
  | **Team** | 1000 | 100 |
27
27
 
28
- On **Free**, the **email API** (`channel: "email"`) is for dashboard/testing; production email via API needs **Pro** or **Team**. **WhatsApp OTP** works on Free (quota applies). See [pricing](https://zindua.run/pricing) for quotas.
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
29
 
30
30
  ```typescript
31
31
  // French version of the same template slug
@@ -59,7 +59,7 @@ Before using the SDK, set up your Zindua project in the dashboard:
59
59
  | Email **Service** connected (Gmail, Outlook, SendGrid, SMTP, …) | `channel: "email"` | Dashboard → **Service** |
60
60
  | WhatsApp connected (QR) | `channel: "whatsapp"` | Dashboard → **WhatsApp** |
61
61
  | Template slug (e.g. `welcome`, `otp-verification`) | All | Dashboard → **Templates** |
62
- | **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** |
63
63
 
64
64
  **Runtime**
65
65
 
@@ -166,9 +166,51 @@ await zindua.send({
166
166
 
167
167
  ```typescript
168
168
  const result = await zindua.send({ /* … */ });
169
- // { 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
+ // }
170
176
  ```
171
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
+ });
205
+ ```
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
+
172
214
  ---
173
215
 
174
216
  ## Errors
@@ -194,10 +236,12 @@ try {
194
236
  ## Security
195
237
 
196
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.
197
240
  - **HTTPS** for `baseUrl` except `http://localhost` in development.
198
241
  - **No credentials in URL** — rejects `baseUrl` with embedded username/password.
199
242
  - **Input validation** — E.164 phones, email format, template slug, variable size limits (aligned with the platform API).
200
243
  - **Redirects disabled** — `fetch` uses `redirect: "error"`.
244
+ - **CORS** — optional origin allowlist in Dashboard → Settings → Integrations (browser calls only).
201
245
 
202
246
  ---
203
247
 
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.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
- 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.2",
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",