@skrybe/sdk 1.0.1 → 2.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 +184 -103
- package/dist/index.cjs +1235 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +764 -0
- package/dist/index.d.ts +737 -25
- package/dist/index.js +1194 -89
- package/dist/index.js.map +1 -0
- package/package.json +48 -22
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Skrybe
|
|
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
CHANGED
|
@@ -1,140 +1,221 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @skrybe/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official TypeScript/JavaScript SDK for the [Skrybe](https://skry.be) API:
|
|
4
|
+
email campaigns, transactional email, lists, subscribers, templates and
|
|
5
|
+
autoresponders.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
- **No dependencies.** It uses the built-in `fetch`, so installing it pulls in
|
|
8
|
+
nothing but this package.
|
|
9
|
+
- **Runs anywhere `fetch` does:** Node.js 18+, Bun, Deno, Cloudflare Workers
|
|
10
|
+
and other edge runtimes. Ships ESM and CommonJS, with types.
|
|
11
|
+
- **Typed errors.** The API often reports failures as a sentence with HTTP 200;
|
|
12
|
+
the SDK turns every one into a `SkrybeError` with a stable `code`.
|
|
13
|
+
|
|
14
|
+
> Your API key can send email as your brand. Use the SDK server-side only, and
|
|
15
|
+
> never ship the key to a browser.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
6
18
|
|
|
7
19
|
```bash
|
|
8
20
|
npm install @skrybe/sdk
|
|
9
21
|
```
|
|
10
22
|
|
|
11
|
-
##
|
|
23
|
+
## Quick start
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
```ts
|
|
26
|
+
import { Skrybe } from '@skrybe/sdk'
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
import { SkrybeSDK } from '@skrybe/sdk';
|
|
28
|
+
const skrybe = new Skrybe({ apiKey: process.env.SKRYBE_API_KEY! })
|
|
17
29
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
const lists = await skrybe.lists.list()
|
|
31
|
+
|
|
32
|
+
await skrybe.subscribers.subscribe({
|
|
33
|
+
listId: lists[0].id,
|
|
34
|
+
email: 'ada@example.com',
|
|
35
|
+
name: 'Ada',
|
|
36
|
+
fields: { Birthday: '1990-12-10' },
|
|
37
|
+
})
|
|
21
38
|
```
|
|
22
39
|
|
|
23
|
-
|
|
40
|
+
CommonJS works too: `const { Skrybe } = require('@skrybe/sdk')`.
|
|
41
|
+
|
|
42
|
+
### Options
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
| Option | Default | |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `apiKey` | required | A brand's API key, from Settings in the Skrybe dashboard. |
|
|
47
|
+
| `baseUrl` | `https://dashboard.skry.be` | |
|
|
48
|
+
| `timeoutMs` | `30000` | Per attempt. |
|
|
49
|
+
| `maxRetries` | `2` | Extra attempts for **reads** after a dropped connection or a 429/502/503/504. Writes are never retried: the API has no idempotency keys, so a retried send could go out twice. |
|
|
50
|
+
| `fetch` | `globalThis.fetch` | Your own fetch, e.g. to go through a proxy or trust a corporate CA. |
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### Email
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// Transactional: one email, delivered immediately. OTPs, resets, receipts.
|
|
58
|
+
await skrybe.emails.sendTransactional({
|
|
59
|
+
toEmail: 'ada@example.com',
|
|
60
|
+
subject: 'Your code',
|
|
61
|
+
htmlBody: '<p>Your code is <strong>384092</strong>.</p>',
|
|
62
|
+
plainBody: 'Your code is 384092.',
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Bulk: up to 250 addresses, or whole lists. Creates a campaign behind the scenes.
|
|
66
|
+
await skrybe.emails.send({
|
|
67
|
+
fromName: 'Ada',
|
|
68
|
+
fromEmail: 'ada@yourdomain.com',
|
|
69
|
+
replyTo: 'ada@yourdomain.com',
|
|
70
|
+
subject: 'Hello [recipient.first]',
|
|
71
|
+
htmlText: '<p>Hi [recipient.first]!</p>',
|
|
32
72
|
to: ['user@example.com'],
|
|
33
|
-
recipientVariables: {
|
|
34
|
-
|
|
35
|
-
first: 'John'
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
trackOpens: 1,
|
|
39
|
-
trackClicks: 1
|
|
40
|
-
});
|
|
73
|
+
recipientVariables: { 'user@example.com': { first: 'John' } },
|
|
74
|
+
})
|
|
41
75
|
```
|
|
42
76
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
const
|
|
47
|
-
fromName: '
|
|
48
|
-
fromEmail: '
|
|
49
|
-
replyTo: '
|
|
50
|
-
title: '
|
|
51
|
-
subject: '
|
|
52
|
-
htmlText: '<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
### Campaigns
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const { campaignId } = await skrybe.campaigns.create({
|
|
81
|
+
fromName: 'Ada',
|
|
82
|
+
fromEmail: 'ada@yourdomain.com',
|
|
83
|
+
replyTo: 'ada@yourdomain.com',
|
|
84
|
+
title: 'October newsletter',
|
|
85
|
+
subject: 'What we shipped',
|
|
86
|
+
htmlText: '<p>…</p>',
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const id = Number(campaignId)
|
|
90
|
+
await skrybe.campaigns.testSend(id, ['me@yourdomain.com'])
|
|
91
|
+
|
|
92
|
+
// Count the recipients without sending, then send.
|
|
93
|
+
const { recipients } = await skrybe.campaigns.send(id, { listIds: [listId], dryRun: true })
|
|
94
|
+
await skrybe.campaigns.send(id, { listIds: [listId] })
|
|
95
|
+
|
|
96
|
+
// Or schedule it. Anything PHP's strtotime() reads.
|
|
97
|
+
await skrybe.campaigns.schedule(id, { listIds: [listId], at: '2027-06-15 18:05', timezone: 'Africa/Lagos' })
|
|
98
|
+
|
|
99
|
+
const stats = await skrybe.campaigns.stats(id)
|
|
100
|
+
const clickers = await skrybe.campaigns.activityAll(id, 'clicks')
|
|
58
101
|
```
|
|
59
102
|
|
|
60
|
-
|
|
103
|
+
Also: `list`, `listAll`, `get`, `update`, `duplicate`, `delete`,
|
|
104
|
+
`unschedule`, `activity`.
|
|
61
105
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
106
|
+
### Lists, subscribers and custom fields
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const list = await skrybe.lists.create({ name: 'Customers', optIn: 'single' })
|
|
110
|
+
await skrybe.lists.addCustomField(list.id, { name: 'Birthday', type: 'date' })
|
|
111
|
+
|
|
112
|
+
const outcome = await skrybe.subscribers.subscribe({ listId: list.id, email: 'ada@example.com' })
|
|
113
|
+
// 'subscribed', or 'already_subscribed' (which updates the name and fields)
|
|
114
|
+
|
|
115
|
+
const status = await skrybe.subscribers.status({ listId: list.id, email: 'ada@example.com' })
|
|
116
|
+
const active = await skrybe.subscribers.listAll(list.id, { status: 'active' })
|
|
66
117
|
```
|
|
67
118
|
|
|
68
|
-
|
|
119
|
+
List ids are encrypted strings: pass them back exactly as the API returns them.
|
|
69
120
|
|
|
70
|
-
|
|
121
|
+
Lists also has `get`, `update`, `delete`, `activeSubscriberCount`,
|
|
122
|
+
`renameCustomField` and `deleteCustomField`. Subscribers also has `unsubscribe`,
|
|
123
|
+
`delete` (removes the row outright) and `list` (one page).
|
|
71
124
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
125
|
+
### Templates
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const t = await skrybe.templates.create({ name: 'Welcome', htmlText: '<p>Welcome!</p>' })
|
|
129
|
+
await skrybe.templates.update(t.id, { fromName: 'Ada' })
|
|
77
130
|
```
|
|
78
131
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
subject:
|
|
87
|
-
htmlText:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
trackClicks?: 0 | 1 | 2;
|
|
95
|
-
scheduleDateTime?: string;
|
|
96
|
-
scheduleTimezone?: string;
|
|
97
|
-
}
|
|
132
|
+
Also `list`, `get` (`{ includeContent: true }` for the HTML) and `delete`.
|
|
133
|
+
|
|
134
|
+
### Autoresponders
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const drip = await skrybe.autoresponders.create({ name: 'Onboarding', type: 'drip', listId })
|
|
138
|
+
await skrybe.autoresponders.createEmail(drip.id, {
|
|
139
|
+
subject: 'Day 3: getting started',
|
|
140
|
+
htmlText: '<p>…</p>',
|
|
141
|
+
when: '+3 days',
|
|
142
|
+
enabled: true, // emails are created switched off unless you say otherwise
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// annually and on_date send on a Date custom field of the list
|
|
146
|
+
await skrybe.autoresponders.create({ name: 'Birthdays', type: 'annually', listId, customField: 'Birthday' })
|
|
98
147
|
```
|
|
99
148
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
title: string;
|
|
108
|
-
subject: string;
|
|
109
|
-
htmlText: string;
|
|
110
|
-
plainText?: string;
|
|
111
|
-
listIds?: string;
|
|
112
|
-
segmentIds?: string;
|
|
113
|
-
excludeListIds?: string;
|
|
114
|
-
excludeSegmentIds?: string;
|
|
115
|
-
queryString?: string;
|
|
116
|
-
trackOpens?: 0 | 1 | 2;
|
|
117
|
-
trackClicks?: 0 | 1 | 2;
|
|
118
|
-
sendCampaign?: 0 | 1;
|
|
119
|
-
scheduleDateTime?: string;
|
|
120
|
-
scheduleTimezone?: string;
|
|
121
|
-
}
|
|
149
|
+
Also `list`, `get` (with its emails) and `emailStats`.
|
|
150
|
+
|
|
151
|
+
### Brands and stats
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const [brand] = await skrybe.brands.list() // the key's own brand
|
|
155
|
+
const sent = await skrybe.stats.totalEmailsSent() // installation-wide, public
|
|
122
156
|
```
|
|
123
157
|
|
|
124
|
-
##
|
|
158
|
+
## Errors
|
|
159
|
+
|
|
160
|
+
Every failure is a `SkrybeError` with a machine-readable `code`, a `type`, and
|
|
161
|
+
often a `hint`. Branch on the subclass or on `type`:
|
|
125
162
|
|
|
126
|
-
|
|
163
|
+
| Class | `type` | When |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `AuthenticationError` | `authentication` | The API key is missing or wrong. |
|
|
166
|
+
| `InvalidRequestError` | `invalid_request` | The request is wrong; retrying it unchanged fails again. |
|
|
167
|
+
| `RateLimitError` | `rate_limit` | A quota, allowance or rate limit was hit. |
|
|
168
|
+
| `ConnectionError` | `connection` | The API could not be reached, or timed out (`code: 'timeout'`). |
|
|
169
|
+
| `ApiError` | `api` | Anything else, e.g. `list_not_found`, `campaign_not_draft`. |
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { SkrybeError, RateLimitError } from '@skrybe/sdk'
|
|
127
173
|
|
|
128
|
-
```typescript
|
|
129
174
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
175
|
+
await skrybe.campaigns.send(id, { listIds: [listId] })
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (err instanceof RateLimitError) {
|
|
178
|
+
// quota_exceeded, rate_limited
|
|
179
|
+
} else if (err instanceof SkrybeError && err.code === 'campaign_not_draft') {
|
|
180
|
+
// already sent
|
|
181
|
+
} else {
|
|
182
|
+
throw err
|
|
183
|
+
}
|
|
135
184
|
}
|
|
136
185
|
```
|
|
137
186
|
|
|
187
|
+
`err.raw` holds the response body and `err.status` the HTTP status, for
|
|
188
|
+
debugging.
|
|
189
|
+
|
|
190
|
+
## Security
|
|
191
|
+
|
|
192
|
+
- **TLS is always verified.** There is no option to turn it off. Use the
|
|
193
|
+
`fetch` option to supply a custom CA or proxy instead.
|
|
194
|
+
- **Keep your runtime's CA store current.** Node.js bundles its own list of
|
|
195
|
+
trusted certificate authorities, so CA distrust events (such as eMudhra's in
|
|
196
|
+
October 2026) only reach you when you update Node, or when you run with
|
|
197
|
+
`--use-system-ca` on an updated OS.
|
|
198
|
+
- **No install scripts and no dependencies**, so there is no supply chain
|
|
199
|
+
beyond this package. Releases are published from GitHub Actions with
|
|
200
|
+
[npm provenance](https://docs.npmjs.com/generating-provenance-statements);
|
|
201
|
+
check with `npm audit signatures`.
|
|
202
|
+
|
|
203
|
+
## Upgrading from 1.x
|
|
204
|
+
|
|
205
|
+
2.0 is a rewrite. Methods moved onto resources and return parsed values instead
|
|
206
|
+
of raw response bodies:
|
|
207
|
+
|
|
208
|
+
| 1.x | 2.x |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `new SkrybeSDK({ apiKey, baseURL })` | `new Skrybe({ apiKey, baseUrl })` |
|
|
211
|
+
| `sendEmail(opts)` | `emails.send(opts)`, returns `{ message, campaignId }` |
|
|
212
|
+
| `sendTransactional(opts)` | `emails.sendTransactional(opts)`, returns `{ message, messageId }` |
|
|
213
|
+
| `createCampaign(opts)` | `campaigns.create(opts)`. `listIds` etc. are now arrays and `sendCampaign: 1` is `send: true`. Returns `{ outcome, campaignId }`. |
|
|
214
|
+
| `getLists(includeHidden)` | `lists.list({ includeHidden })`, returns `[{ id, name }]` |
|
|
215
|
+
|
|
216
|
+
Failures now throw instead of resolving with an error string. The axios
|
|
217
|
+
dependency is gone.
|
|
218
|
+
|
|
138
219
|
## License
|
|
139
220
|
|
|
140
|
-
MIT
|
|
221
|
+
MIT
|