@notifykit/sdk 1.0.4 → 1.1.0

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
@@ -74,6 +74,41 @@ await client.sendEmail({
74
74
  });
75
75
  ```
76
76
 
77
+ ### Per-Message Provider Routing (Paid Plans)
78
+
79
+ By default, NotifyKit picks the email provider based on your configured priority order and falls back through all of them on failure. To pin a specific provider per message, pass `provider`. To narrow the failover to one alternative, pass `fallback`.
80
+
81
+ ```typescript
82
+ // Force this email through SendGrid; if it fails, the job fails.
83
+ await client.sendEmail({
84
+ to: "user@example.com",
85
+ subject: "Receipt",
86
+ body: "<h1>Thanks</h1>",
87
+ provider: "SENDGRID",
88
+ });
89
+
90
+ // Force SendGrid first, then Resend if SendGrid fails. No other providers tried.
91
+ await client.sendEmail({
92
+ to: "user@example.com",
93
+ subject: "Receipt",
94
+ body: "<h1>Thanks</h1>",
95
+ provider: "SENDGRID",
96
+ fallback: "RESEND",
97
+ });
98
+ ```
99
+
100
+ **Validation:**
101
+
102
+ | Case | Outcome |
103
+ | ------------------------------------------------------------------ | ----------------- |
104
+ | `fallback` set without `provider` | `400 Bad Request` |
105
+ | `provider` equals `fallback` | `400 Bad Request` |
106
+ | Requested `provider` or `fallback` not configured for your account | `400 Bad Request` |
107
+
108
+ Forced routing is a contract: NotifyKit does **not** retry through providers you didn't authorize. The routing fields persist with the job, so manual or automatic retries replay the same attempt set.
109
+
110
+ To inspect which provider actually delivered (or which one was last attempted on failure), see [Tracking Jobs](#tracking-jobs) — `getJob(id)` returns a `deliveryLogs[]` array with a `usedProvider` field on each entry.
111
+
77
112
  ### Prevent Duplicate Sends
78
113
 
79
114
  ```typescript
@@ -114,7 +149,7 @@ await client.sendWebhook({
114
149
  ```typescript
115
150
  await client.sendWebhook({
116
151
  url: "https://yourapp.com/webhooks/order",
117
- method: "POST", // GET, POST, PUT, PATCH, DELETE — default is POST
152
+ method: "POST",
118
153
  payload: { orderId: "12345" },
119
154
  headers: {
120
155
  "X-Webhook-Secret": process.env.WEBHOOK_SECRET!,
@@ -159,13 +194,30 @@ if (status.status === "completed") {
159
194
  }
160
195
  ```
161
196
 
197
+ ### Inspect Delivery Attempts
198
+
199
+ `getJob(id)` returns a `deliveryLogs[]` array — one entry per delivery attempt — with the provider that was used:
200
+
201
+ ```typescript
202
+ const status = await client.getJob(job.jobId);
203
+
204
+ for (const log of status.deliveryLogs) {
205
+ console.log(
206
+ `attempt ${log.attempt} via ${log.usedProvider ?? "unknown"}: ${log.status}`,
207
+ );
208
+ if (log.errorMessage) console.log(` error: ${log.errorMessage}`);
209
+ }
210
+ ```
211
+
212
+ For successful sends, the last entry's `usedProvider` is the provider that delivered. For failures, it's the last provider attempted. Webhook jobs return an empty array.
213
+
162
214
  ### List Jobs with Filters
163
215
 
164
216
  ```typescript
165
217
  const result = await client.listJobs({
166
218
  page: 1,
167
219
  limit: 20,
168
- type: "email", // Filter by type: 'email' or 'webhook'
220
+ type: "email", // Filter by type: 'email' or 'webhook'
169
221
  status: "failed", // Filter by status
170
222
  });
171
223
 
@@ -210,11 +262,12 @@ try {
210
262
  await client.sendEmail({ to: "bad-email", subject: "Test", body: "Hello" });
211
263
  } catch (error) {
212
264
  if (error instanceof NotifyKitError) {
213
- console.error(error.getFullMessage()); // "[400] to must be an email"
265
+ console.error(error.getFullMessage());
214
266
 
215
267
  if (error.isStatus(400)) console.error("Bad request:", error.message);
216
268
  if (error.isStatus(401)) console.error("Invalid API key");
217
- if (error.isStatus(403)) console.error("Quota or permission error:", error.message);
269
+ if (error.isStatus(403))
270
+ console.error("Quota or permission error:", error.message);
218
271
  if (error.isStatus(409)) console.error("Duplicate idempotency key");
219
272
  if (error.isStatus(429)) console.error("Rate limit exceeded");
220
273
  }
@@ -225,15 +278,15 @@ try {
225
278
 
226
279
  ## API Reference
227
280
 
228
- | Method | Description | Returns |
229
- | ---------------------- | ----------------------------------- | ------------------------------ |
230
- | `sendEmail(options)` | Send an email notification | `Promise<JobResponse>` |
231
- | `sendWebhook(options)` | Send a webhook notification | `Promise<JobResponse>` |
232
- | `getJob(jobId)` | Get job status and details | `Promise<JobStatus>` |
233
- | `listJobs(options?)` | List jobs with optional filters | `Promise<{ data, pagination }>` |
234
- | `retryJob(jobId)` | Retry a failed job | `Promise<RetryJobResponse>` |
235
- | `ping()` | Test API connection | `Promise<string>` |
236
- | `getApiInfo()` | Get API version info | `Promise<ApiInfo>` |
281
+ | Method | Description | Returns |
282
+ | ---------------------- | ------------------------------- | ------------------------------- |
283
+ | `sendEmail(options)` | Send an email notification | `Promise<JobResponse>` |
284
+ | `sendWebhook(options)` | Send a webhook notification | `Promise<JobResponse>` |
285
+ | `getJob(jobId)` | Get job status and details | `Promise<JobStatus>` |
286
+ | `listJobs(options?)` | List jobs with optional filters | `Promise<{ data, pagination }>` |
287
+ | `retryJob(jobId)` | Retry a failed job | `Promise<RetryJobResponse>` |
288
+ | `ping()` | Test API connection | `Promise<string>` |
289
+ | `getApiInfo()` | Get API version info | `Promise<ApiInfo>` |
237
290
 
238
291
  ### TypeScript Types
239
292
 
@@ -251,11 +304,11 @@ import type {
251
304
 
252
305
  ## Plans
253
306
 
254
- | Plan | Price | Webhooks/month | Emails/month |
255
- | ------- | --------- | -------------- | ------------------------------------- |
256
- | Free | $0 | 100 (shared) | 100 (shared with webhooks) |
257
- | Indie | $9/mo | 4,000 | Unlimited (via your SendGrid key) |
258
- | Startup | $30/mo | 15,000 | Unlimited (via your SendGrid key) |
307
+ | Plan | Price | Webhooks/month | Emails/month |
308
+ | ------- | ------ | -------------- | --------------------------------- |
309
+ | Free | $0 | 100 (shared) | 100 (shared with webhooks) |
310
+ | Indie | $9/mo | 4,000 | Unlimited (via your SendGrid key) |
311
+ | Startup | $30/mo | 15,000 | Unlimited (via your SendGrid key) |
259
312
 
260
313
  ---
261
314
 
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NotifyKitConfig, SendEmailOptions, SendWebhookOptions, JobResponse, JobStatus, ApiInfo, PaginationMeta, RetryJobResponse } from './types';
1
+ import { NotifyKitConfig, SendEmailOptions, SendWebhookOptions, JobResponse, JobStatus, JobSummary, ApiInfo, PaginationMeta, RetryJobResponse } from './types';
2
2
  export declare class NotifyKitClient {
3
3
  private client;
4
4
  constructor(config: NotifyKitConfig);
@@ -19,7 +19,7 @@ export declare class NotifyKitClient {
19
19
  type?: 'email' | 'webhook';
20
20
  status?: 'pending' | 'processing' | 'completed' | 'failed';
21
21
  }): Promise<{
22
- data: JobStatus[];
22
+ data: JobSummary[];
23
23
  pagination: PaginationMeta;
24
24
  }>;
25
25
  /** Retry a failed job */
package/dist/client.js CHANGED
@@ -48,7 +48,8 @@ class NotifyKitClient {
48
48
  message = data.message;
49
49
  }
50
50
  }
51
- throw new errors_1.NotifyKitError(message, statusCode, data, errors);
51
+ const retryAfter = statusCode === 429 && typeof data?.retryAfter === 'number' ? data.retryAfter : undefined;
52
+ throw new errors_1.NotifyKitError(message, statusCode, data, errors, retryAfter);
52
53
  }
53
54
  throw new errors_1.NotifyKitError(error.message || 'Network error occurred');
54
55
  });
package/dist/errors.d.ts CHANGED
@@ -2,7 +2,8 @@ export declare class NotifyKitError extends Error {
2
2
  statusCode?: number | undefined;
3
3
  response?: any | undefined;
4
4
  errors?: any[] | undefined;
5
- constructor(message: string, statusCode?: number | undefined, response?: any | undefined, errors?: any[] | undefined);
5
+ retryAfter?: number | undefined;
6
+ constructor(message: string, statusCode?: number | undefined, response?: any | undefined, errors?: any[] | undefined, retryAfter?: number | undefined);
6
7
  /**
7
8
  * Get formatted error message with details
8
9
  */
package/dist/errors.js CHANGED
@@ -2,11 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NotifyKitError = void 0;
4
4
  class NotifyKitError extends Error {
5
- constructor(message, statusCode, response, errors) {
5
+ constructor(message, statusCode, response, errors, retryAfter) {
6
6
  super(message);
7
7
  this.statusCode = statusCode;
8
8
  this.response = response;
9
9
  this.errors = errors;
10
+ this.retryAfter = retryAfter;
10
11
  this.name = "NotifyKitError";
11
12
  Error.captureStackTrace(this, this.constructor);
12
13
  }
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface PaginationMeta {
8
8
  limit: number;
9
9
  totalPages: number;
10
10
  }
11
+ export type EmailProvider = "SENDGRID" | "RESEND" | "POSTMARK";
11
12
  export interface SendEmailOptions {
12
13
  to: string;
13
14
  subject: string;
@@ -15,6 +16,16 @@ export interface SendEmailOptions {
15
16
  from?: string;
16
17
  priority?: 1 | 5 | 10;
17
18
  idempotencyKey?: string;
19
+ /**
20
+ * Force this email through a specific configured provider (paid plans only).
21
+ * If unset, the customer's priority order with full failover applies.
22
+ */
23
+ provider?: EmailProvider;
24
+ /**
25
+ * Fallback provider to try if `provider` fails. Ignored unless `provider`
26
+ * is set. Other configured providers are not tried.
27
+ */
28
+ fallback?: EmailProvider;
18
29
  }
19
30
  export interface SendWebhookOptions {
20
31
  url: string;
@@ -30,6 +41,14 @@ export interface JobResponse {
30
41
  type: string;
31
42
  createdAt: string;
32
43
  }
44
+ export interface DeliveryLog {
45
+ id: string;
46
+ attempt: number;
47
+ status: string;
48
+ usedProvider: EmailProvider | null;
49
+ errorMessage: string | null;
50
+ createdAt: string;
51
+ }
33
52
  export interface JobStatus {
34
53
  id: string;
35
54
  type: string;
@@ -42,6 +61,17 @@ export interface JobStatus {
42
61
  createdAt: string;
43
62
  startedAt?: string;
44
63
  completedAt?: string;
64
+ deliveryLogs: DeliveryLog[];
65
+ }
66
+ export interface JobSummary {
67
+ id: string;
68
+ type: string;
69
+ status: "pending" | "processing" | "completed" | "failed";
70
+ priority: number;
71
+ attempts: number;
72
+ errorMessage?: string;
73
+ createdAt: string;
74
+ completedAt?: string;
45
75
  }
46
76
  export interface RetryJobResponse {
47
77
  jobId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notifykit/sdk",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Official NotifyKit SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",