@notifykit/sdk 1.0.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/LICENSE +21 -0
- package/README.md +622 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.js +94 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +35 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +21 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +2 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NotifyKit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
# NotifyKit SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js/TypeScript SDK for NotifyKit - Send emails and webhooks with ease.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @notifykit/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { NotifyKitClient } from "@notifykit/sdk";
|
|
15
|
+
|
|
16
|
+
const notifyKit = new NotifyKitClient({
|
|
17
|
+
apiKey: "ntfy_sk_your_api_key",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Send an email
|
|
21
|
+
await notifyKit.sendEmail({
|
|
22
|
+
to: "user@example.com",
|
|
23
|
+
subject: "Welcome!",
|
|
24
|
+
body: "<h1>Hello World</h1>",
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Sending Emails
|
|
31
|
+
|
|
32
|
+
**Important:** Emails are queued immediately (HTTP 202 Accepted) and delivered asynchronously. Check job status to confirm delivery.
|
|
33
|
+
|
|
34
|
+
### Basic Email
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
await notifyKit.sendEmail({
|
|
38
|
+
to: "user@example.com",
|
|
39
|
+
subject: "Order Confirmed",
|
|
40
|
+
body: "<h1>Thanks for your order!</h1>",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Custom From Address
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
await notifyKit.sendEmail({
|
|
48
|
+
to: "user@example.com",
|
|
49
|
+
subject: "Welcome",
|
|
50
|
+
body: "<h1>Hello</h1>",
|
|
51
|
+
from: "hello@yourapp.com", // Must be a verified domain
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Prevent Duplicate Sends (Recommended)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
await notifyKit.sendEmail({
|
|
59
|
+
to: "user@example.com",
|
|
60
|
+
subject: "Welcome",
|
|
61
|
+
body: "<h1>Hello</h1>",
|
|
62
|
+
idempotencyKey: "user-123-welcome", // Prevents duplicate sends on retry
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**How idempotency works:**
|
|
67
|
+
|
|
68
|
+
- Same key → Request rejected with 409 Conflict, original job returned
|
|
69
|
+
- Different key → New email sent
|
|
70
|
+
- No key → Always sends (not recommended for production)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Sending Webhooks
|
|
75
|
+
|
|
76
|
+
**Important:** Like emails, webhooks are queued immediately (HTTP 202 Accepted) and delivered asynchronously. Check job status to confirm delivery.
|
|
77
|
+
|
|
78
|
+
### Basic Webhook
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
await notifyKit.sendWebhook({
|
|
82
|
+
url: "https://yourapp.com/webhooks/order",
|
|
83
|
+
payload: {
|
|
84
|
+
orderId: "12345",
|
|
85
|
+
status: "completed",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### With Custom Headers and Method
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await notifyKit.sendWebhook({
|
|
94
|
+
url: "https://yourapp.com/webhooks/order",
|
|
95
|
+
method: "POST", // GET, POST, PUT, PATCH, DELETE(default is POST)
|
|
96
|
+
payload: { orderId: "12345" },
|
|
97
|
+
headers: {
|
|
98
|
+
"X-Event-Type": "order.created",
|
|
99
|
+
"X-Signature": "abc123",
|
|
100
|
+
},
|
|
101
|
+
idempotencyKey: "order-12345-webhook",
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Tracking Jobs
|
|
108
|
+
|
|
109
|
+
All notifications return a job object with a job Id that can be tracked.
|
|
110
|
+
|
|
111
|
+
### Check Job Status
|
|
112
|
+
|
|
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
|
+
```typescript
|
|
116
|
+
const job = await notifyKit.sendEmail({
|
|
117
|
+
to: "user@example.com",
|
|
118
|
+
subject: "Test",
|
|
119
|
+
body: "<h1>Test</h1>",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
console.log(`Job ID: ${job.jobId}`); // Save this for later tracking
|
|
123
|
+
|
|
124
|
+
// Check status later
|
|
125
|
+
const status = await notifyKit.getJob(job.jobId);
|
|
126
|
+
|
|
127
|
+
console.log(status.status); // 'pending' | 'processing' | 'completed' | 'failed'
|
|
128
|
+
|
|
129
|
+
if (status.status === "completed") {
|
|
130
|
+
console.log("Email sent successfully!");
|
|
131
|
+
} else if (status.status === "failed") {
|
|
132
|
+
console.error(" Failed:", status.errorMessage);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### List Jobs with Filters
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const result = await notifyKit.listJobs({
|
|
140
|
+
page: 1,
|
|
141
|
+
limit: 20,
|
|
142
|
+
type: "email", // Filter by type: 'email' or 'webhook'
|
|
143
|
+
status: "failed", // Filter by status: 'pending', 'processing', 'completed', 'failed'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
console.log(`Total: ${result.pagination.total} jobs`);
|
|
147
|
+
console.log(`Pages: ${result.pagination.totalPages}`);
|
|
148
|
+
|
|
149
|
+
result.data.forEach((job) => {
|
|
150
|
+
console.log(`${job.id}: ${job.status} (${job.attempts} attempts)`);
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Retry Failed Jobs
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const job = await notifyKit.sendEmail({...});
|
|
158
|
+
|
|
159
|
+
// Later, if job failed
|
|
160
|
+
const status = await notifyKit.getJob(job.jobId);
|
|
161
|
+
|
|
162
|
+
if (status.status === "failed") {
|
|
163
|
+
// Retry the job
|
|
164
|
+
await notifyKit.retryJob(job.jobId);
|
|
165
|
+
console.log("Job queued for retry");
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Note:** Only jobs with status `failed` can be retried.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Domain Management
|
|
174
|
+
|
|
175
|
+
Use your own verified domain to send emails (e.g., `welcome@yourapp.com` instead of `noreply@notifykit.dev`).
|
|
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.
|
|
351
|
+
|
|
352
|
+
**If you try to use the main domain:**
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
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)
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Real-World Examples
|
|
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
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
async function sendEmailWithMonitoring(
|
|
541
|
+
to: string,
|
|
542
|
+
subject: string,
|
|
543
|
+
body: string
|
|
544
|
+
) {
|
|
545
|
+
const job = await notifyKit.sendEmail({ to, subject, body });
|
|
546
|
+
|
|
547
|
+
// Check status after 5 seconds
|
|
548
|
+
setTimeout(async () => {
|
|
549
|
+
const status = await notifyKit.getJob(job.jobId);
|
|
550
|
+
|
|
551
|
+
if (status.status === "failed") {
|
|
552
|
+
console.error(`Email delivery failed: ${status.errorMessage}`);
|
|
553
|
+
await alertAdmin(`Email to ${to} failed`);
|
|
554
|
+
}
|
|
555
|
+
}, 5000);
|
|
556
|
+
|
|
557
|
+
return job;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
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
|
+
## API Reference
|
|
577
|
+
|
|
578
|
+
| Method | Description | Returns |
|
|
579
|
+
| ----------------------------------- | -------------------------------- | ---------------------------- |
|
|
580
|
+
| `sendEmail(options)` | Send an email notification | `JobResponse` |
|
|
581
|
+
| `sendWebhook(options)` | Send a webhook notification | `JobResponse` |
|
|
582
|
+
| `getJob(jobId)` | Get job status by ID | `JobStatus` |
|
|
583
|
+
| `listJobs(options?)` | List jobs with optional filters | `{ data, pagination }` |
|
|
584
|
+
| `retryJob(jobId)` | Retry a failed job | `JobResponse` |
|
|
585
|
+
| `requestDomainVerification(domain)` | Request domain verification | `DomainVerificationResponse` |
|
|
586
|
+
| `verifyDomain()` | Check domain DNS verification | `DomainStatusResponse` |
|
|
587
|
+
| `getDomainStatus()` | Get current domain configuration | `DomainInfoResponse` |
|
|
588
|
+
| `removeDomain()` | Remove domain configuration | `{ message: string }` |
|
|
589
|
+
|
|
590
|
+
### TypeScript Types
|
|
591
|
+
|
|
592
|
+
All methods are fully typed. Import types for use in your code:
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
import {
|
|
596
|
+
NotifyKitClient,
|
|
597
|
+
SendEmailOptions,
|
|
598
|
+
SendWebhookOptions,
|
|
599
|
+
JobResponse,
|
|
600
|
+
JobStatus,
|
|
601
|
+
NotifyKitError,
|
|
602
|
+
} from "@notifykit/sdk";
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Requirements
|
|
608
|
+
|
|
609
|
+
- Node.js 16+ or browser with fetch support
|
|
610
|
+
- TypeScript 4.5+ (optional, for type checking)
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Support
|
|
615
|
+
|
|
616
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/brayzonn/notifykit-sdk/issues)
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## License
|
|
621
|
+
|
|
622
|
+
MIT © NotifyKit
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NotifyKitConfig, SendEmailOptions, SendWebhookOptions, JobResponse, JobStatus, ApiInfo, PaginationMeta } from "./types";
|
|
2
|
+
export declare class NotifyKitClient {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(config: NotifyKitConfig);
|
|
5
|
+
/** Test API connection */
|
|
6
|
+
ping(): Promise<string>;
|
|
7
|
+
/** Get API information */
|
|
8
|
+
getApiInfo(): Promise<ApiInfo>;
|
|
9
|
+
/** Send an email notification */
|
|
10
|
+
sendEmail(options: SendEmailOptions): Promise<JobResponse>;
|
|
11
|
+
/** Send a webhook notification */
|
|
12
|
+
sendWebhook(options: SendWebhookOptions): Promise<JobResponse>;
|
|
13
|
+
/** Get job status by ID */
|
|
14
|
+
getJob(jobId: string): Promise<JobStatus>;
|
|
15
|
+
/** List jobs with optional filters */
|
|
16
|
+
listJobs(options?: {
|
|
17
|
+
page?: number;
|
|
18
|
+
limit?: number;
|
|
19
|
+
type?: "email" | "webhook";
|
|
20
|
+
status?: "pending" | "processing" | "completed" | "failed";
|
|
21
|
+
}): Promise<{
|
|
22
|
+
data: JobStatus[];
|
|
23
|
+
pagination: PaginationMeta;
|
|
24
|
+
}>;
|
|
25
|
+
/** Retry a failed job */
|
|
26
|
+
retryJob(jobId: string): Promise<JobResponse>;
|
|
27
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NotifyKitClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
class NotifyKitClient {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
if (!config.apiKey) {
|
|
12
|
+
throw new Error("API key is required");
|
|
13
|
+
}
|
|
14
|
+
this.client = axios_1.default.create({
|
|
15
|
+
baseURL: config.baseUrl || "https://api.notifykit.dev",
|
|
16
|
+
headers: {
|
|
17
|
+
"X-API-Key": config.apiKey,
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
this.client.interceptors.response.use((response) => {
|
|
22
|
+
const apiResponse = response.data?.data;
|
|
23
|
+
if (!apiResponse)
|
|
24
|
+
return response.data;
|
|
25
|
+
if (apiResponse.message)
|
|
26
|
+
return apiResponse.message;
|
|
27
|
+
return apiResponse;
|
|
28
|
+
}, (error) => {
|
|
29
|
+
if (error.response) {
|
|
30
|
+
const data = error.response.data;
|
|
31
|
+
const statusCode = error.response.status;
|
|
32
|
+
let message = error.message;
|
|
33
|
+
let errors = null;
|
|
34
|
+
if (data?.error) {
|
|
35
|
+
if (Array.isArray(data.error)) {
|
|
36
|
+
errors = data.error;
|
|
37
|
+
message = data.error.join(", ");
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
message = data.error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (data?.message) {
|
|
44
|
+
if (Array.isArray(data.message)) {
|
|
45
|
+
errors = data.message;
|
|
46
|
+
message = "Validation failed";
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
message = data.message;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new errors_1.NotifyKitError(message, statusCode, data, errors);
|
|
53
|
+
}
|
|
54
|
+
throw new errors_1.NotifyKitError(error.message || "Network error occurred");
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// ================================
|
|
58
|
+
// APP
|
|
59
|
+
// ================================
|
|
60
|
+
/** Test API connection */
|
|
61
|
+
async ping() {
|
|
62
|
+
return await this.client.get("/api/v1/ping");
|
|
63
|
+
}
|
|
64
|
+
/** Get API information */
|
|
65
|
+
async getApiInfo() {
|
|
66
|
+
return await this.client.get("/api/v1/info");
|
|
67
|
+
}
|
|
68
|
+
// ================================
|
|
69
|
+
// NOTIFICATIONS
|
|
70
|
+
// ================================
|
|
71
|
+
/** Send an email notification */
|
|
72
|
+
async sendEmail(options) {
|
|
73
|
+
return await this.client.post("/api/v1/notifications/email", options);
|
|
74
|
+
}
|
|
75
|
+
/** Send a webhook notification */
|
|
76
|
+
async sendWebhook(options) {
|
|
77
|
+
return await this.client.post("/api/v1/notifications/webhook", options);
|
|
78
|
+
}
|
|
79
|
+
/** Get job status by ID */
|
|
80
|
+
async getJob(jobId) {
|
|
81
|
+
return await this.client.get(`/api/v1/notifications/jobs/${jobId}`);
|
|
82
|
+
}
|
|
83
|
+
/** List jobs with optional filters */
|
|
84
|
+
async listJobs(options) {
|
|
85
|
+
return await this.client.get("/api/v1/notifications/jobs", {
|
|
86
|
+
params: options,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Retry a failed job */
|
|
90
|
+
async retryJob(jobId) {
|
|
91
|
+
return await this.client.post(`/api/v1/notifications/jobs/${jobId}/retry`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.NotifyKitClient = NotifyKitClient;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class NotifyKitError extends Error {
|
|
2
|
+
statusCode?: number | undefined;
|
|
3
|
+
response?: any | undefined;
|
|
4
|
+
errors?: any[] | undefined;
|
|
5
|
+
constructor(message: string, statusCode?: number | undefined, response?: any | undefined, errors?: any[] | undefined);
|
|
6
|
+
/**
|
|
7
|
+
* Get formatted error message with details
|
|
8
|
+
*/
|
|
9
|
+
getFullMessage(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Check if error is a specific HTTP status
|
|
12
|
+
*/
|
|
13
|
+
isStatus(code: number): boolean;
|
|
14
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NotifyKitError = void 0;
|
|
4
|
+
class NotifyKitError extends Error {
|
|
5
|
+
constructor(message, statusCode, response, errors) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.response = response;
|
|
9
|
+
this.errors = errors;
|
|
10
|
+
this.name = "NotifyKitError";
|
|
11
|
+
Error.captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get formatted error message with details
|
|
15
|
+
*/
|
|
16
|
+
getFullMessage() {
|
|
17
|
+
let msg = this.message;
|
|
18
|
+
if (this.statusCode) {
|
|
19
|
+
msg = `[${this.statusCode}] ${msg}`;
|
|
20
|
+
}
|
|
21
|
+
if (this.errors && this.errors.length > 0) {
|
|
22
|
+
msg +=
|
|
23
|
+
"\nValidation errors:\n" +
|
|
24
|
+
this.errors.map((e) => ` - ${e}`).join("\n");
|
|
25
|
+
}
|
|
26
|
+
return msg;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if error is a specific HTTP status
|
|
30
|
+
*/
|
|
31
|
+
isStatus(code) {
|
|
32
|
+
return this.statusCode === code;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.NotifyKitError = NotifyKitError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.NotifyKitClient = void 0;
|
|
18
|
+
var client_1 = require("./client");
|
|
19
|
+
Object.defineProperty(exports, "NotifyKitClient", { enumerable: true, get: function () { return client_1.NotifyKitClient; } });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
__exportStar(require("./errors"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface NotifyKitConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface PaginationMeta {
|
|
6
|
+
total: number;
|
|
7
|
+
page: number;
|
|
8
|
+
limit: number;
|
|
9
|
+
totalPages: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SendEmailOptions {
|
|
12
|
+
to: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
body: string;
|
|
15
|
+
from?: string;
|
|
16
|
+
idempotencyKey?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ApiInfo {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
description: string;
|
|
22
|
+
documentation: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SendWebhookOptions {
|
|
25
|
+
url: string;
|
|
26
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
27
|
+
payload?: any;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
idempotencyKey?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface JobResponse {
|
|
32
|
+
jobId: string;
|
|
33
|
+
status: string;
|
|
34
|
+
type: string;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
}
|
|
37
|
+
export interface JobStatus {
|
|
38
|
+
id: string;
|
|
39
|
+
type: string;
|
|
40
|
+
status: "pending" | "processing" | "completed" | "failed";
|
|
41
|
+
attempts: number;
|
|
42
|
+
errorMessage?: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
completedAt?: string;
|
|
45
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notifykit/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official NotifyKit SDK for Node.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=14.0.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"notifykit",
|
|
21
|
+
"notifications",
|
|
22
|
+
"email",
|
|
23
|
+
"webhook",
|
|
24
|
+
"api"
|
|
25
|
+
],
|
|
26
|
+
"author": "brayzonn",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/brayzonn/notifykit-sdk.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/brayzonn/notifykit-sdk/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/brayzonn/notifykit-sdk#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"axios": "^1.6.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|