@notifykit/sdk 1.0.0 → 1.0.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/README.md +97 -448
- package/dist/client.d.ts +4 -4
- package/dist/client.js +17 -18
- package/dist/types.d.ts +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NotifyKit SDK
|
|
2
2
|
|
|
3
|
-
Official Node.js/TypeScript SDK for NotifyKit
|
|
3
|
+
Official Node.js/TypeScript SDK for NotifyKit — send emails and webhooks with automatic retries and delivery tracking.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,20 +8,29 @@ Official Node.js/TypeScript SDK for NotifyKit - Send emails and webhooks with ea
|
|
|
8
8
|
npm install @notifykit/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
**Requirements:** Node.js 18+
|
|
12
|
+
|
|
11
13
|
## Quick Start
|
|
12
14
|
|
|
13
15
|
```typescript
|
|
14
16
|
import { NotifyKitClient } from "@notifykit/sdk";
|
|
15
17
|
|
|
16
|
-
const
|
|
17
|
-
apiKey:
|
|
18
|
+
const client = new NotifyKitClient({
|
|
19
|
+
apiKey: process.env.NOTIFYKIT_API_KEY!, // nh_...
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
// Send an email
|
|
21
|
-
await
|
|
23
|
+
const emailJob = await client.sendEmail({
|
|
22
24
|
to: "user@example.com",
|
|
23
25
|
subject: "Welcome!",
|
|
24
26
|
body: "<h1>Hello World</h1>",
|
|
27
|
+
idempotencyKey: "welcome-user-123",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Send a webhook
|
|
31
|
+
const webhookJob = await client.sendWebhook({
|
|
32
|
+
url: "https://your-app.com/webhooks/events",
|
|
33
|
+
payload: { event: "user.created", userId: "123" },
|
|
25
34
|
});
|
|
26
35
|
```
|
|
27
36
|
|
|
@@ -29,33 +38,46 @@ await notifyKit.sendEmail({
|
|
|
29
38
|
|
|
30
39
|
## Sending Emails
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
Emails are queued immediately (HTTP 202 Accepted) and delivered asynchronously. Use [getJob](#tracking-jobs) to confirm delivery.
|
|
42
|
+
|
|
43
|
+
> **Note:** On the **Free plan**, emails send from NotifyKit's shared SendGrid account (`noreply@notifykit.dev`). On **Indie/Startup plans**, connect your own SendGrid API key in the dashboard before sending emails.
|
|
33
44
|
|
|
34
45
|
### Basic Email
|
|
35
46
|
|
|
36
47
|
```typescript
|
|
37
|
-
await
|
|
48
|
+
await client.sendEmail({
|
|
38
49
|
to: "user@example.com",
|
|
39
50
|
subject: "Order Confirmed",
|
|
40
51
|
body: "<h1>Thanks for your order!</h1>",
|
|
41
52
|
});
|
|
42
53
|
```
|
|
43
54
|
|
|
44
|
-
### Custom From Address
|
|
55
|
+
### Custom From Address (Paid Plans)
|
|
45
56
|
|
|
46
57
|
```typescript
|
|
47
|
-
await
|
|
58
|
+
await client.sendEmail({
|
|
48
59
|
to: "user@example.com",
|
|
49
60
|
subject: "Welcome",
|
|
50
61
|
body: "<h1>Hello</h1>",
|
|
51
|
-
from: "hello@yourapp.com", // Must be a verified domain
|
|
62
|
+
from: "hello@em.yourapp.com", // Must be a verified domain
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### High Priority
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
await client.sendEmail({
|
|
70
|
+
to: "user@example.com",
|
|
71
|
+
subject: "Password Reset",
|
|
72
|
+
body: "<p>Click here to reset your password.</p>",
|
|
73
|
+
priority: 1, // 1=high, 5=normal (default), 10=low
|
|
52
74
|
});
|
|
53
75
|
```
|
|
54
76
|
|
|
55
|
-
### Prevent Duplicate Sends
|
|
77
|
+
### Prevent Duplicate Sends
|
|
56
78
|
|
|
57
79
|
```typescript
|
|
58
|
-
await
|
|
80
|
+
await client.sendEmail({
|
|
59
81
|
to: "user@example.com",
|
|
60
82
|
subject: "Welcome",
|
|
61
83
|
body: "<h1>Hello</h1>",
|
|
@@ -65,20 +87,20 @@ await notifyKit.sendEmail({
|
|
|
65
87
|
|
|
66
88
|
**How idempotency works:**
|
|
67
89
|
|
|
68
|
-
- Same key → Request rejected with 409 Conflict
|
|
90
|
+
- Same key → Request rejected with `409 Conflict`, original job returned
|
|
69
91
|
- Different key → New email sent
|
|
70
|
-
- No key → Always sends (not recommended for
|
|
92
|
+
- No key → Always sends (not recommended for critical transactional emails)
|
|
71
93
|
|
|
72
94
|
---
|
|
73
95
|
|
|
74
96
|
## Sending Webhooks
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
Webhooks are queued immediately (HTTP 202 Accepted) and delivered asynchronously with automatic retries on failure.
|
|
77
99
|
|
|
78
100
|
### Basic Webhook
|
|
79
101
|
|
|
80
102
|
```typescript
|
|
81
|
-
await
|
|
103
|
+
await client.sendWebhook({
|
|
82
104
|
url: "https://yourapp.com/webhooks/order",
|
|
83
105
|
payload: {
|
|
84
106
|
orderId: "12345",
|
|
@@ -90,61 +112,64 @@ await notifyKit.sendWebhook({
|
|
|
90
112
|
### With Custom Headers and Method
|
|
91
113
|
|
|
92
114
|
```typescript
|
|
93
|
-
await
|
|
115
|
+
await client.sendWebhook({
|
|
94
116
|
url: "https://yourapp.com/webhooks/order",
|
|
95
|
-
method: "POST", // GET, POST, PUT, PATCH, DELETE
|
|
117
|
+
method: "POST", // GET, POST, PUT, PATCH, DELETE — default is POST
|
|
96
118
|
payload: { orderId: "12345" },
|
|
97
119
|
headers: {
|
|
120
|
+
"X-Webhook-Secret": process.env.WEBHOOK_SECRET!,
|
|
98
121
|
"X-Event-Type": "order.created",
|
|
99
|
-
"X-Signature": "abc123",
|
|
100
122
|
},
|
|
101
123
|
idempotencyKey: "order-12345-webhook",
|
|
102
124
|
});
|
|
103
125
|
```
|
|
104
126
|
|
|
127
|
+
**Retry behavior:**
|
|
128
|
+
|
|
129
|
+
- Max 3 attempts, exponential backoff (~2s, ~4s, ~8s)
|
|
130
|
+
- Retried on 5xx errors, network failures, timeouts
|
|
131
|
+
- Not retried on 4xx errors
|
|
132
|
+
|
|
105
133
|
---
|
|
106
134
|
|
|
107
135
|
## Tracking Jobs
|
|
108
136
|
|
|
109
|
-
|
|
137
|
+
Every notification returns a job ID you can use to track delivery status.
|
|
110
138
|
|
|
111
139
|
### Check Job Status
|
|
112
140
|
|
|
113
|
-
**Important:** `sendEmail()` returns immediately after queuing the job (HTTP 202 Accepted). The actual email is sent asynchronously by a background worker. Always check the job status to confirm delivery.
|
|
114
|
-
|
|
115
141
|
```typescript
|
|
116
|
-
const job = await
|
|
142
|
+
const job = await client.sendEmail({
|
|
117
143
|
to: "user@example.com",
|
|
118
144
|
subject: "Test",
|
|
119
145
|
body: "<h1>Test</h1>",
|
|
120
146
|
});
|
|
121
147
|
|
|
122
|
-
console.log(`Job ID: ${job.jobId}`);
|
|
148
|
+
console.log(`Job ID: ${job.jobId}`);
|
|
123
149
|
|
|
124
150
|
// Check status later
|
|
125
|
-
const status = await
|
|
151
|
+
const status = await client.getJob(job.jobId);
|
|
126
152
|
|
|
127
153
|
console.log(status.status); // 'pending' | 'processing' | 'completed' | 'failed'
|
|
128
154
|
|
|
129
155
|
if (status.status === "completed") {
|
|
130
|
-
console.log("
|
|
156
|
+
console.log("Delivered successfully!");
|
|
131
157
|
} else if (status.status === "failed") {
|
|
132
|
-
console.error("
|
|
158
|
+
console.error("Failed:", status.errorMessage);
|
|
133
159
|
}
|
|
134
160
|
```
|
|
135
161
|
|
|
136
162
|
### List Jobs with Filters
|
|
137
163
|
|
|
138
164
|
```typescript
|
|
139
|
-
const result = await
|
|
165
|
+
const result = await client.listJobs({
|
|
140
166
|
page: 1,
|
|
141
167
|
limit: 20,
|
|
142
|
-
type: "email",
|
|
143
|
-
status: "failed", // Filter by status
|
|
168
|
+
type: "email", // Filter by type: 'email' or 'webhook'
|
|
169
|
+
status: "failed", // Filter by status
|
|
144
170
|
});
|
|
145
171
|
|
|
146
172
|
console.log(`Total: ${result.pagination.total} jobs`);
|
|
147
|
-
console.log(`Pages: ${result.pagination.totalPages}`);
|
|
148
173
|
|
|
149
174
|
result.data.forEach((job) => {
|
|
150
175
|
console.log(`${job.id}: ${job.status} (${job.attempts} attempts)`);
|
|
@@ -154,466 +179,90 @@ result.data.forEach((job) => {
|
|
|
154
179
|
### Retry Failed Jobs
|
|
155
180
|
|
|
156
181
|
```typescript
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
// Later, if job failed
|
|
160
|
-
const status = await notifyKit.getJob(job.jobId);
|
|
182
|
+
const status = await client.getJob(job.jobId);
|
|
161
183
|
|
|
162
184
|
if (status.status === "failed") {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log("Job queued for retry");
|
|
185
|
+
const message = await client.retryJob(job.jobId);
|
|
186
|
+
console.log(message); // "Job has been re-queued for processing"
|
|
166
187
|
}
|
|
167
188
|
```
|
|
168
189
|
|
|
169
|
-
|
|
190
|
+
Only jobs with `failed` status can be retried.
|
|
170
191
|
|
|
171
192
|
---
|
|
172
193
|
|
|
173
194
|
## Domain Management
|
|
174
195
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
### Step 1: Request Domain Verification
|
|
178
|
-
|
|
179
|
-
```typescript
|
|
180
|
-
const verification = await notifyKit.requestDomainVerification("yourapp.com");
|
|
181
|
-
|
|
182
|
-
console.log(`Domain: ${verification.domain}`);
|
|
183
|
-
console.log(`Status: ${verification.status}`); // 'pending' or 'verified'
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Response:**
|
|
187
|
-
|
|
188
|
-
```json
|
|
189
|
-
{
|
|
190
|
-
"domain": "yourapp.com",
|
|
191
|
-
"status": "pending",
|
|
192
|
-
"dnsRecords": [
|
|
193
|
-
{
|
|
194
|
-
"id": 1,
|
|
195
|
-
"type": "CNAME",
|
|
196
|
-
"host": "em8724.yourapp.com",
|
|
197
|
-
"value": "u12345678.wl123.sendgrid.net",
|
|
198
|
-
"description": "Mail CNAME - Routes email through SendGrid"
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
"id": 2,
|
|
202
|
-
"type": "CNAME",
|
|
203
|
-
"host": "s1._domainkey.yourapp.com",
|
|
204
|
-
"value": "s1.domainkey.u12345678.wl123.sendgrid.net",
|
|
205
|
-
"description": "DKIM 1 - Email authentication (prevents spoofing)"
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
"id": 3,
|
|
209
|
-
"type": "CNAME",
|
|
210
|
-
"host": "s2._domainkey.yourapp.com",
|
|
211
|
-
"value": "s2.domainkey.u12345678.wl123.sendgrid.net",
|
|
212
|
-
"description": "DKIM 2 - Email authentication (backup)"
|
|
213
|
-
}
|
|
214
|
-
],
|
|
215
|
-
"instructions": {
|
|
216
|
-
"message": "Add these DNS records to your domain registrar",
|
|
217
|
-
"steps": [
|
|
218
|
-
"1. Login to your domain registrar (Namecheap, GoDaddy, Cloudflare, etc.)",
|
|
219
|
-
"2. Navigate to DNS settings for your domain",
|
|
220
|
-
"3. Add each CNAME record below",
|
|
221
|
-
"4. Wait 15-60 minutes for DNS propagation",
|
|
222
|
-
"5. Click 'Verify Domain' to check status"
|
|
223
|
-
],
|
|
224
|
-
"estimatedTime": "15-60 minutes"
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Understanding DNS Records
|
|
230
|
-
|
|
231
|
-
**Record 1: Mail CNAME** (`em8724.yourapp.com`)
|
|
232
|
-
|
|
233
|
-
- Routes emails through SendGrid's infrastructure
|
|
234
|
-
- Allows sending from `welcome@em.yourapp.com`
|
|
235
|
-
|
|
236
|
-
**Record 2 & 3: DKIM Records** (`s1._domainkey` and `s2._domainkey`)
|
|
237
|
-
|
|
238
|
-
- Digital signatures that prove emails are from you
|
|
239
|
-
- Prevents spoofing and improves deliverability
|
|
240
|
-
- Two records provide redundancy during key rotation
|
|
241
|
-
|
|
242
|
-
### Step 2: Add DNS Records to Your Domain
|
|
243
|
-
|
|
244
|
-
Go to your domain registrar and add the 3 CNAME records:
|
|
245
|
-
|
|
246
|
-
**Example for Cloudflare:**
|
|
247
|
-
|
|
248
|
-
```
|
|
249
|
-
Type: CNAME
|
|
250
|
-
Name: em8724
|
|
251
|
-
Content: u12345678.wl123.sendgrid.net
|
|
252
|
-
Proxy status: DNS only (disable proxy)
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
**Example for Namecheap:**
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
Type: CNAME Record
|
|
259
|
-
Host: em8724
|
|
260
|
-
Value: u12345678.wl123.sendgrid.net
|
|
261
|
-
TTL: Automatic
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**DNS propagation takes 15-60 minutes** (sometimes up to 24 hours).
|
|
265
|
-
|
|
266
|
-
### Step 3: Verify Domain
|
|
267
|
-
|
|
268
|
-
After adding DNS records and waiting for propagation:
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
const status = await notifyKit.verifyDomain();
|
|
272
|
-
|
|
273
|
-
if (status.verified) {
|
|
274
|
-
console.log("Domain verified!");
|
|
275
|
-
console.log("You can now send from: welcome@em.yourapp.com");
|
|
276
|
-
} else {
|
|
277
|
-
console.log("DNS still propagating...");
|
|
278
|
-
console.log(status.message);
|
|
279
|
-
|
|
280
|
-
// Check validation results for issues
|
|
281
|
-
if (status.validationResults) {
|
|
282
|
-
console.log("Validation details:", status.validationResults);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
**Response:**
|
|
288
|
-
|
|
289
|
-
```json
|
|
290
|
-
{
|
|
291
|
-
"domain": "yourapp.com",
|
|
292
|
-
"verified": true,
|
|
293
|
-
"message": "Domain verified! You can now send emails from this domain."
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Step 4: Send Emails from Your Domain
|
|
298
|
-
|
|
299
|
-
Once verified, emails automatically use `noreply@em.yourapp.com` if you don't specify a `from` address:
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
// Automatically uses: noreply@em.yourapp.com
|
|
303
|
-
await notifyKit.sendEmail({
|
|
304
|
-
to: "user@example.com",
|
|
305
|
-
subject: "Welcome!",
|
|
306
|
-
body: "Welcome to MyApp!",
|
|
307
|
-
// No 'from' specified → Uses noreply@em.yourapp.com
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Or specify a custom sender address on your domain
|
|
311
|
-
await notifyKit.sendEmail({
|
|
312
|
-
to: "user@example.com",
|
|
313
|
-
subject: "Support Response",
|
|
314
|
-
body: "We're here to help",
|
|
315
|
-
from: "support@em.yourapp.com", // Custom sender
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// You can also use different names with the same domain
|
|
319
|
-
await notifyKit.sendEmail({
|
|
320
|
-
to: "user@example.com",
|
|
321
|
-
subject: "Order Shipped",
|
|
322
|
-
body: "Your order is on the way!",
|
|
323
|
-
from: "orders@em.yourapp.com", // Different sender
|
|
324
|
-
});
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
**Important: The `em.` Subdomain**
|
|
328
|
-
|
|
329
|
-
Emails are sent from `em.yourapp.com` (subdomain), **not** `yourapp.com` (main domain).
|
|
330
|
-
|
|
331
|
-
**Examples:**
|
|
332
|
-
|
|
333
|
-
- Correct: `welcome@em.yourapp.com`
|
|
334
|
-
- Correct: `support@em.yourapp.com`
|
|
335
|
-
- Wrong: `welcome@yourapp.com` (will be rejected)
|
|
336
|
-
|
|
337
|
-
**Why the subdomain?**
|
|
338
|
-
|
|
339
|
-
Using a dedicated subdomain (`em.`) for transactional emails protects your main domain's reputation:
|
|
340
|
-
|
|
341
|
-
1. **Isolation**: If a transactional email is marked as spam, it doesn't hurt your main domain
|
|
342
|
-
2. **Separate reputation**: Your business emails (`team@yourapp.com`) remain trusted
|
|
343
|
-
|
|
344
|
-
**What recipients see:**
|
|
345
|
-
|
|
346
|
-
```
|
|
347
|
-
From: support@em.yourapp.com
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
Most email clients only show `yourapp.com` in the sender name, so users typically see it as coming from your domain.
|
|
196
|
+
Domain verification is managed through the **NotifyKit dashboard** (Settings → Domain). Verified domains let you send emails from your own address (e.g., `support@em.yourapp.com`) instead of `noreply@notifykit.dev`.
|
|
351
197
|
|
|
352
|
-
|
|
198
|
+
See [Domain Verification](https://docs.notifykit.dev/docs/guides/domain-verification) for setup instructions.
|
|
353
199
|
|
|
354
|
-
|
|
355
|
-
await notifyKit.sendEmail({
|
|
356
|
-
from: "hello@yourapp.com", // Missing "em."
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Error: "Cannot send from hello@yourapp.com. Use em.yourapp.com instead
|
|
360
|
-
// (e.g., hello@em.yourapp.com)"
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Check Domain Status Anytime
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
const info = await notifyKit.getDomainStatus();
|
|
367
|
-
|
|
368
|
-
console.log(`Domain: ${info.domain}`); // 'yourapp.com' or null
|
|
369
|
-
console.log(`Verified: ${info.verified}`); // true or false
|
|
370
|
-
console.log(`Status: ${info.status}`); // 'not_configured', 'pending', 'verified'
|
|
371
|
-
|
|
372
|
-
if (info.verified) {
|
|
373
|
-
console.log(`Verified at: ${info.verifiedAt}`);
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
**Response:**
|
|
378
|
-
|
|
379
|
-
```json
|
|
380
|
-
{
|
|
381
|
-
"domain": "yourapp.com",
|
|
382
|
-
"verified": true,
|
|
383
|
-
"status": "verified",
|
|
384
|
-
"dnsRecords": [...],
|
|
385
|
-
"requestedAt": "2026-01-04T10:00:00.000Z",
|
|
386
|
-
"verifiedAt": "2026-01-04T10:45:00.000Z"
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Remove Domain Configuration
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
await notifyKit.removeDomain();
|
|
394
|
-
console.log(
|
|
395
|
-
"Domain removed. Emails will now send from NotifyKit's default domain."
|
|
396
|
-
);
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
**Note:** You can only have **one verified domain at a time**. Requesting a new domain automatically replaces the old one.
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
## Troubleshooting Domain Verification
|
|
404
|
-
|
|
405
|
-
### DNS Not Verifying After 1 Hour
|
|
406
|
-
|
|
407
|
-
**Check DNS propagation:**
|
|
408
|
-
|
|
409
|
-
```bash
|
|
410
|
-
# Check if CNAME exists
|
|
411
|
-
dig em8724.yourapp.com CNAME
|
|
412
|
-
|
|
413
|
-
# Should return: u12345678.wl123.sendgrid.net
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
**Common issues:**
|
|
417
|
-
|
|
418
|
-
1. **Wrong host name** - Some registrars need full hostname (`em8724.yourapp.com`), others just the subdomain (`em8724`)
|
|
419
|
-
2. **Proxy enabled** (Cloudflare) - Disable proxy, use "DNS only"
|
|
420
|
-
3. **TTL too high** - Lower TTL to 300 seconds (5 minutes) for faster propagation
|
|
421
|
-
4. **Old records** - Delete any existing records for the same hostname first
|
|
422
|
-
|
|
423
|
-
### Emails Still Sending from NotifyKit Domain
|
|
424
|
-
|
|
425
|
-
**Possible causes:**
|
|
426
|
-
|
|
427
|
-
1. Domain not verified yet - Check status: `await notifyKit.getDomainStatus()`
|
|
428
|
-
2. Free plan - Custom domains only available on paid plans (Indie, Startup)
|
|
429
|
-
3. Verification failed - DNS records may have been removed
|
|
430
|
-
|
|
431
|
-
### Verification Says "Domain Already Verified by Another Customer"
|
|
432
|
-
|
|
433
|
-
Each domain can only be verified by one NotifyKit account. If you own this domain:
|
|
434
|
-
|
|
435
|
-
1. Remove it from the other account first
|
|
436
|
-
2. Then verify it on your current account
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
## Best Practices
|
|
441
|
-
|
|
442
|
-
### Use Subdomain for Transactional Emails
|
|
443
|
-
|
|
444
|
-
**Good:** `noreply@em.yourapp.com` (dedicated subdomain)
|
|
445
|
-
**Bad:** `noreply@yourapp.com` (main domain)
|
|
446
|
-
|
|
447
|
-
**Why?** If transactional emails are marked as spam, it doesn't hurt your main domain's reputation for important business emails.
|
|
448
|
-
|
|
449
|
-
### Set Up DMARC (Optional but Recommended)
|
|
450
|
-
|
|
451
|
-
Add a TXT record to improve email deliverability:
|
|
452
|
-
|
|
453
|
-
```
|
|
454
|
-
Type: TXT
|
|
455
|
-
Name: _dmarc
|
|
456
|
-
Value: v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
This enables email authentication reporting and protects against spoofing.
|
|
460
|
-
|
|
461
|
-
### Monitor Your Domain Reputation
|
|
462
|
-
|
|
463
|
-
- Check SendGrid Activity Feed regularly
|
|
464
|
-
- Watch for high bounce rates (>5% is concerning)
|
|
465
|
-
- Remove invalid email addresses from your lists
|
|
466
|
-
- Never send unsolicited emails (spam)
|
|
200
|
+
**Available on Indie and Startup plans only.**
|
|
467
201
|
|
|
468
202
|
---
|
|
469
203
|
|
|
470
|
-
##
|
|
471
|
-
|
|
472
|
-
### User Signup Flow
|
|
473
|
-
|
|
474
|
-
```typescript
|
|
475
|
-
import { NotifyKitClient } from "@notifykit/sdk";
|
|
476
|
-
|
|
477
|
-
const notifyKit = new NotifyKitClient({
|
|
478
|
-
apiKey: process.env.NOTIFYKIT_API_KEY,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
async function handleUserSignup(user) {
|
|
482
|
-
await notifyKit.sendEmail({
|
|
483
|
-
to: user.email,
|
|
484
|
-
subject: "Welcome to MyApp!",
|
|
485
|
-
body: `
|
|
486
|
-
<h1>Hi ${user.name}!</h1>
|
|
487
|
-
<p>Thanks for signing up. Get started by exploring our features.</p>
|
|
488
|
-
<a href="https://myapp.com/get-started">Get Started</a>
|
|
489
|
-
`,
|
|
490
|
-
idempotencyKey: `user-${user.id}-welcome`,
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
### Order Confirmation with Multiple Notifications
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
async function handleOrderPlaced(order) {
|
|
499
|
-
// Send email to customer
|
|
500
|
-
await notifyKit.sendEmail({
|
|
501
|
-
to: order.customerEmail,
|
|
502
|
-
subject: `Order #${order.id} Confirmed`,
|
|
503
|
-
body: generateOrderConfirmationEmail(order),
|
|
504
|
-
idempotencyKey: `order-${order.id}-customer-email`,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Notify warehouse system via webhook
|
|
508
|
-
await notifyKit.sendWebhook({
|
|
509
|
-
url: "https://warehouse-system.com/api/new-order",
|
|
510
|
-
method: "POST",
|
|
511
|
-
payload: {
|
|
512
|
-
orderId: order.id,
|
|
513
|
-
items: order.items,
|
|
514
|
-
shippingAddress: order.shippingAddress,
|
|
515
|
-
priority: order.priority,
|
|
516
|
-
},
|
|
517
|
-
headers: {
|
|
518
|
-
"X-Warehouse-Token": process.env.WAREHOUSE_API_TOKEN,
|
|
519
|
-
},
|
|
520
|
-
idempotencyKey: `order-${order.id}-warehouse-webhook`,
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// Send to analytics
|
|
524
|
-
await notifyKit.sendWebhook({
|
|
525
|
-
url: "https://analytics.myapp.com/track",
|
|
526
|
-
payload: {
|
|
527
|
-
event: "order.placed",
|
|
528
|
-
orderId: order.id,
|
|
529
|
-
revenue: order.total,
|
|
530
|
-
customerId: order.customerId,
|
|
531
|
-
},
|
|
532
|
-
idempotencyKey: `order-${order.id}-analytics`,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### Background Job Status Monitoring
|
|
204
|
+
## Error Handling
|
|
538
205
|
|
|
539
206
|
```typescript
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
body:
|
|
544
|
-
) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
}, 5000);
|
|
556
|
-
|
|
557
|
-
return job;
|
|
207
|
+
import { NotifyKitClient, NotifyKitError } from "@notifykit/sdk";
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await client.sendEmail({ to: "bad-email", subject: "Test", body: "Hello" });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof NotifyKitError) {
|
|
213
|
+
console.error(error.getFullMessage()); // "[400] to must be an email"
|
|
214
|
+
|
|
215
|
+
if (error.isStatus(400)) console.error("Bad request:", error.message);
|
|
216
|
+
if (error.isStatus(401)) console.error("Invalid API key");
|
|
217
|
+
if (error.isStatus(403)) console.error("Quota or permission error:", error.message);
|
|
218
|
+
if (error.isStatus(409)) console.error("Duplicate idempotency key");
|
|
219
|
+
if (error.isStatus(429)) console.error("Rate limit exceeded");
|
|
220
|
+
}
|
|
558
221
|
}
|
|
559
222
|
```
|
|
560
223
|
|
|
561
224
|
---
|
|
562
225
|
|
|
563
|
-
## Configuration
|
|
564
|
-
|
|
565
|
-
### Custom Base URL (Self-Hosted or Staging)
|
|
566
|
-
|
|
567
|
-
```typescript
|
|
568
|
-
const notifyKit = new NotifyKitClient({
|
|
569
|
-
apiKey: "ntfy_sk_...",
|
|
570
|
-
baseUrl: "https://staging-api.notifykit.dev", // Default: https://api.notifykit.dev
|
|
571
|
-
});
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
---
|
|
575
|
-
|
|
576
226
|
## API Reference
|
|
577
227
|
|
|
578
|
-
| Method
|
|
579
|
-
|
|
|
580
|
-
| `sendEmail(options)`
|
|
581
|
-
| `sendWebhook(options)`
|
|
582
|
-
| `getJob(jobId)`
|
|
583
|
-
| `listJobs(options?)`
|
|
584
|
-
| `retryJob(jobId)`
|
|
585
|
-
| `
|
|
586
|
-
| `
|
|
587
|
-
| `getDomainStatus()` | Get current domain configuration | `DomainInfoResponse` |
|
|
588
|
-
| `removeDomain()` | Remove domain configuration | `{ message: string }` |
|
|
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<JobDetails>` |
|
|
233
|
+
| `listJobs(options?)` | List jobs with optional filters | `Promise<{ data, pagination }>` |
|
|
234
|
+
| `retryJob(jobId)` | Retry a failed job | `Promise<string>` |
|
|
235
|
+
| `ping()` | Test API connection | `Promise<string>` |
|
|
236
|
+
| `getApiInfo()` | Get API version info | `Promise<ApiInfo>` |
|
|
589
237
|
|
|
590
238
|
### TypeScript Types
|
|
591
239
|
|
|
592
|
-
All methods are fully typed. Import types for use in your code:
|
|
593
|
-
|
|
594
240
|
```typescript
|
|
595
|
-
import {
|
|
596
|
-
|
|
241
|
+
import type {
|
|
242
|
+
NotifyKitConfig,
|
|
597
243
|
SendEmailOptions,
|
|
598
244
|
SendWebhookOptions,
|
|
599
245
|
JobResponse,
|
|
600
|
-
|
|
601
|
-
NotifyKitError,
|
|
246
|
+
ApiInfo,
|
|
602
247
|
} from "@notifykit/sdk";
|
|
603
248
|
```
|
|
604
249
|
|
|
605
250
|
---
|
|
606
251
|
|
|
607
|
-
##
|
|
252
|
+
## Plans
|
|
608
253
|
|
|
609
|
-
|
|
610
|
-
|
|
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) |
|
|
611
259
|
|
|
612
260
|
---
|
|
613
261
|
|
|
614
262
|
## Support
|
|
615
263
|
|
|
616
|
-
-
|
|
264
|
+
- Docs: [docs.notifykit.dev](https://docs.notifykit.dev)
|
|
265
|
+
- Issues: [GitHub Issues](https://github.com/brayzonn/notifykit-sdk/issues)
|
|
617
266
|
|
|
618
267
|
---
|
|
619
268
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotifyKitConfig, SendEmailOptions, SendWebhookOptions, JobResponse, JobStatus, ApiInfo, PaginationMeta } from
|
|
1
|
+
import { NotifyKitConfig, SendEmailOptions, SendWebhookOptions, JobResponse, JobStatus, ApiInfo, PaginationMeta, RetryJobResponse } from './types';
|
|
2
2
|
export declare class NotifyKitClient {
|
|
3
3
|
private client;
|
|
4
4
|
constructor(config: NotifyKitConfig);
|
|
@@ -16,12 +16,12 @@ export declare class NotifyKitClient {
|
|
|
16
16
|
listJobs(options?: {
|
|
17
17
|
page?: number;
|
|
18
18
|
limit?: number;
|
|
19
|
-
type?:
|
|
20
|
-
status?:
|
|
19
|
+
type?: 'email' | 'webhook';
|
|
20
|
+
status?: 'pending' | 'processing' | 'completed' | 'failed';
|
|
21
21
|
}): Promise<{
|
|
22
22
|
data: JobStatus[];
|
|
23
23
|
pagination: PaginationMeta;
|
|
24
24
|
}>;
|
|
25
25
|
/** Retry a failed job */
|
|
26
|
-
retryJob(jobId: string): Promise<
|
|
26
|
+
retryJob(jobId: string): Promise<RetryJobResponse>;
|
|
27
27
|
}
|
package/dist/client.js
CHANGED
|
@@ -9,22 +9,21 @@ const errors_1 = require("./errors");
|
|
|
9
9
|
class NotifyKitClient {
|
|
10
10
|
constructor(config) {
|
|
11
11
|
if (!config.apiKey) {
|
|
12
|
-
throw new Error(
|
|
12
|
+
throw new Error('API key is required');
|
|
13
13
|
}
|
|
14
14
|
this.client = axios_1.default.create({
|
|
15
|
-
baseURL: config.baseUrl ||
|
|
15
|
+
baseURL: config.baseUrl || 'https://api.notifykit.dev',
|
|
16
16
|
headers: {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
'X-API-Key': config.apiKey,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
this.client.interceptors.response.use((response) => {
|
|
22
|
-
const apiResponse = response.data
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return apiResponse;
|
|
22
|
+
const apiResponse = response.data;
|
|
23
|
+
if (apiResponse && apiResponse.success === false) {
|
|
24
|
+
throw new errors_1.NotifyKitError(apiResponse.error || apiResponse.message || 'Request failed', response.status, apiResponse);
|
|
25
|
+
}
|
|
26
|
+
return apiResponse?.data ?? apiResponse;
|
|
28
27
|
}, (error) => {
|
|
29
28
|
if (error.response) {
|
|
30
29
|
const data = error.response.data;
|
|
@@ -34,7 +33,7 @@ class NotifyKitClient {
|
|
|
34
33
|
if (data?.error) {
|
|
35
34
|
if (Array.isArray(data.error)) {
|
|
36
35
|
errors = data.error;
|
|
37
|
-
message = data.error.join(
|
|
36
|
+
message = data.error.join(', ');
|
|
38
37
|
}
|
|
39
38
|
else {
|
|
40
39
|
message = data.error;
|
|
@@ -43,7 +42,7 @@ class NotifyKitClient {
|
|
|
43
42
|
else if (data?.message) {
|
|
44
43
|
if (Array.isArray(data.message)) {
|
|
45
44
|
errors = data.message;
|
|
46
|
-
message =
|
|
45
|
+
message = 'Validation failed';
|
|
47
46
|
}
|
|
48
47
|
else {
|
|
49
48
|
message = data.message;
|
|
@@ -51,7 +50,7 @@ class NotifyKitClient {
|
|
|
51
50
|
}
|
|
52
51
|
throw new errors_1.NotifyKitError(message, statusCode, data, errors);
|
|
53
52
|
}
|
|
54
|
-
throw new errors_1.NotifyKitError(error.message ||
|
|
53
|
+
throw new errors_1.NotifyKitError(error.message || 'Network error occurred');
|
|
55
54
|
});
|
|
56
55
|
}
|
|
57
56
|
// ================================
|
|
@@ -59,22 +58,22 @@ class NotifyKitClient {
|
|
|
59
58
|
// ================================
|
|
60
59
|
/** Test API connection */
|
|
61
60
|
async ping() {
|
|
62
|
-
return await this.client.get(
|
|
61
|
+
return await this.client.get('/api/v1/ping');
|
|
63
62
|
}
|
|
64
63
|
/** Get API information */
|
|
65
64
|
async getApiInfo() {
|
|
66
|
-
return await this.client.get(
|
|
65
|
+
return await this.client.get('/api/v1/info');
|
|
67
66
|
}
|
|
68
67
|
// ================================
|
|
69
68
|
// NOTIFICATIONS
|
|
70
69
|
// ================================
|
|
71
70
|
/** Send an email notification */
|
|
72
71
|
async sendEmail(options) {
|
|
73
|
-
return await this.client.post(
|
|
72
|
+
return await this.client.post('/api/v1/notifications/email', options);
|
|
74
73
|
}
|
|
75
74
|
/** Send a webhook notification */
|
|
76
75
|
async sendWebhook(options) {
|
|
77
|
-
return await this.client.post(
|
|
76
|
+
return await this.client.post('/api/v1/notifications/webhook', options);
|
|
78
77
|
}
|
|
79
78
|
/** Get job status by ID */
|
|
80
79
|
async getJob(jobId) {
|
|
@@ -82,7 +81,7 @@ class NotifyKitClient {
|
|
|
82
81
|
}
|
|
83
82
|
/** List jobs with optional filters */
|
|
84
83
|
async listJobs(options) {
|
|
85
|
-
return await this.client.get(
|
|
84
|
+
return await this.client.get('/api/v1/notifications/jobs', {
|
|
86
85
|
params: options,
|
|
87
86
|
});
|
|
88
87
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface SendEmailOptions {
|
|
|
13
13
|
subject: string;
|
|
14
14
|
body: string;
|
|
15
15
|
from?: string;
|
|
16
|
+
priority?: number;
|
|
16
17
|
idempotencyKey?: string;
|
|
17
18
|
}
|
|
18
19
|
export interface ApiInfo {
|
|
@@ -23,7 +24,7 @@ export interface ApiInfo {
|
|
|
23
24
|
}
|
|
24
25
|
export interface SendWebhookOptions {
|
|
25
26
|
url: string;
|
|
26
|
-
method?:
|
|
27
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
27
28
|
payload?: any;
|
|
28
29
|
headers?: Record<string, string>;
|
|
29
30
|
idempotencyKey?: string;
|
|
@@ -37,9 +38,14 @@ export interface JobResponse {
|
|
|
37
38
|
export interface JobStatus {
|
|
38
39
|
id: string;
|
|
39
40
|
type: string;
|
|
40
|
-
status:
|
|
41
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
41
42
|
attempts: number;
|
|
42
43
|
errorMessage?: string;
|
|
43
44
|
createdAt: string;
|
|
44
45
|
completedAt?: string;
|
|
45
46
|
}
|
|
47
|
+
export interface RetryJobResponse {
|
|
48
|
+
jobId: string;
|
|
49
|
+
status: string;
|
|
50
|
+
message: string;
|
|
51
|
+
}
|