@sendcraft/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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SendCraft
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,326 @@
1
+ # sendcraft-sdk
2
+
3
+ <p align="center">
4
+ <img src="https://sendcraft.online/logo.png" alt="SendCraft" width="72" />
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>Official Node.js / TypeScript SDK for <a href="https://sendcraft.online">SendCraft</a></strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/sendcraft-sdk"><img src="https://img.shields.io/npm/v/sendcraft-sdk?color=6366f1&label=npm" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/sendcraft-sdk"><img src="https://img.shields.io/npm/dm/sendcraft-sdk?color=10b981&label=downloads" alt="downloads" /></a>
14
+ <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="node" />
15
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT" />
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install sendcraft-sdk
24
+ ```
25
+
26
+ **React Email support** (optional):
27
+ ```bash
28
+ npm install @react-email/render react react-dom
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ```typescript
36
+ import { SendCraft } from 'sendcraft-sdk';
37
+
38
+ const client = new SendCraft({ apiKey: process.env.SENDCRAFT_API_KEY });
39
+
40
+ // Send a transactional email
41
+ await client.emails.send({
42
+ to: 'user@example.com',
43
+ subject: 'Hello!',
44
+ html: '<h1>Welcome aboard</h1>',
45
+ });
46
+ ```
47
+
48
+ Get your API key from the [SendCraft Dashboard](https://sendcraft.online/dashboard/settings).
49
+
50
+ ---
51
+
52
+ ## API Reference
53
+
54
+ ### `client.emails`
55
+
56
+ #### `emails.send(options)`
57
+
58
+ ```typescript
59
+ await client.emails.send({
60
+ to: 'user@example.com',
61
+ toName: 'Alice',
62
+ subject: 'Your order shipped',
63
+ html: '<p>Your order is on the way!</p>',
64
+ text: 'Your order is on the way!', // optional plain-text fallback
65
+ from: 'orders@mystore.com', // optional, defaults to account default
66
+ fromName: 'My Store',
67
+ replyTo: 'support@mystore.com',
68
+ cc: 'manager@mystore.com',
69
+ bcc: ['audit@mystore.com'],
70
+ idempotencyKey: 'order-123-shipped', // prevent duplicate sends
71
+ });
72
+ ```
73
+
74
+ #### `emails.batch(emails[])`
75
+
76
+ Send up to 100 emails in one request.
77
+
78
+ ```typescript
79
+ await client.emails.batch([
80
+ { to: 'alice@example.com', subject: 'Hi Alice', html: '<p>Hello</p>' },
81
+ { to: 'bob@example.com', subject: 'Hi Bob', html: '<p>Hello</p>' },
82
+ ]);
83
+ ```
84
+
85
+ #### `emails.schedule(options)`
86
+
87
+ ```typescript
88
+ await client.emails.schedule({
89
+ to: 'user@example.com',
90
+ subject: 'Reminder',
91
+ html: '<p>Just a reminder!</p>',
92
+ scheduledAt: new Date('2026-04-01T09:00:00Z'),
93
+ });
94
+ ```
95
+
96
+ #### `emails.cancelSchedule(id)` · `emails.updateSchedule(id, scheduledAt)`
97
+
98
+ ```typescript
99
+ await client.emails.cancelSchedule('email_id');
100
+ await client.emails.updateSchedule('email_id', new Date('2026-05-01T09:00:00Z'));
101
+ ```
102
+
103
+ #### `emails.get(id)` · `emails.list(options?)` · `emails.stats()` · `emails.export(options?)`
104
+
105
+ ```typescript
106
+ const email = await client.emails.get('email_id');
107
+ const { emails, total } = await client.emails.list({ page: 1, limit: 20, status: 'sent' });
108
+ const { stats } = await client.emails.stats();
109
+ const csvUrl = await client.emails.export({ days: 30 });
110
+ ```
111
+
112
+ #### `emails.render(options)` — React Email (no send)
113
+
114
+ ```typescript
115
+ import WelcomeEmail from './emails/WelcomeEmail';
116
+
117
+ const html = await client.emails.render({ react: <WelcomeEmail name="Alice" /> });
118
+ ```
119
+
120
+ ---
121
+
122
+ ### `client.campaigns`
123
+
124
+ ```typescript
125
+ // Create
126
+ const { campaign } = await client.campaigns.create({
127
+ name: 'Summer Sale',
128
+ subject: 'Big discounts inside',
129
+ from: 'deals@mystore.com',
130
+ html: '<h1>Up to 50% off!</h1>',
131
+ recipients: ['a@example.com', 'b@example.com'],
132
+ });
133
+
134
+ // Send
135
+ await client.campaigns.send('campaign_id');
136
+
137
+ // List / Get / Update / Delete
138
+ await client.campaigns.list({ page: 1, limit: 10 });
139
+ await client.campaigns.get('campaign_id');
140
+ await client.campaigns.update('campaign_id', { subject: 'Updated subject' });
141
+ await client.campaigns.delete('campaign_id');
142
+
143
+ // Analytics
144
+ const { analytics } = await client.campaigns.analytics('campaign_id');
145
+ ```
146
+
147
+ ---
148
+
149
+ ### `client.subscribers`
150
+
151
+ ```typescript
152
+ await client.subscribers.add({
153
+ email: 'user@example.com',
154
+ listId: 'list_id',
155
+ firstName: 'Alice',
156
+ customFields: { plan: 'pro' },
157
+ });
158
+
159
+ await client.subscribers.import({
160
+ listId: 'list_id',
161
+ subscribers: [{ email: 'a@b.com' }, { email: 'c@d.com' }],
162
+ });
163
+
164
+ await client.subscribers.list({ page: 1, limit: 50, status: 'active' });
165
+ await client.subscribers.update('subscriber_id', { firstName: 'Alicia' });
166
+ await client.subscribers.delete('subscriber_id');
167
+ ```
168
+
169
+ ---
170
+
171
+ ### `client.templates`
172
+
173
+ ```typescript
174
+ const { template } = await client.templates.create({
175
+ name: 'Welcome Email',
176
+ subject: 'Welcome to {{appName}}',
177
+ html: '<p>Hi {{firstName}}, welcome!</p>',
178
+ });
179
+
180
+ await client.templates.list();
181
+ await client.templates.get('template_id');
182
+ await client.templates.update('template_id', { subject: 'New subject' });
183
+ await client.templates.delete('template_id');
184
+ ```
185
+
186
+ ---
187
+
188
+ ### `client.domains`
189
+
190
+ ```typescript
191
+ const { domain, dnsRecords } = await client.domains.add('myapp.com');
192
+ dnsRecords.forEach(r => console.log(`${r.type} ${r.name} → ${r.value}`));
193
+
194
+ await client.domains.verify(domain._id);
195
+ await client.domains.list();
196
+ await client.domains.get('domain_id');
197
+ await client.domains.delete('domain_id');
198
+ ```
199
+
200
+ ---
201
+
202
+ ### `client.webhooks`
203
+
204
+ ```typescript
205
+ await client.webhooks.create({
206
+ url: 'https://myapp.com/hooks/sendcraft',
207
+ events: ['email.bounced', 'email.opened', 'email.clicked'],
208
+ });
209
+
210
+ await client.webhooks.list();
211
+ await client.webhooks.delete('webhook_id');
212
+ ```
213
+
214
+ #### Signature Verification
215
+
216
+ ```typescript
217
+ app.post('/hooks/sendcraft', express.raw({ type: 'application/json' }), (req, res) => {
218
+ const valid = client.webhooks.verify(
219
+ req.body.toString(),
220
+ req.headers['x-sendcraft-signature'] as string,
221
+ process.env.SENDCRAFT_WEBHOOK_SECRET!,
222
+ );
223
+ if (!valid) return res.sendStatus(401);
224
+
225
+ const event = client.webhooks.parse(req.body.toString());
226
+ // event.type: 'email.bounced' | 'email.opened' | 'email.clicked' | ...
227
+ res.sendStatus(200);
228
+ });
229
+ ```
230
+
231
+ ---
232
+
233
+ ### `client.analytics`
234
+
235
+ ```typescript
236
+ await client.analytics.overview();
237
+ await client.analytics.daily(30);
238
+ await client.analytics.campaign('campaign_id');
239
+ ```
240
+
241
+ ---
242
+
243
+ ### `client.segments` · `client.automation` · `client.smtp` · `client.apiKeys` · `client.topics`
244
+
245
+ ```typescript
246
+ // Segments
247
+ await client.segments.list();
248
+ await client.segments.create({ name: 'VIP users', rules: [...] });
249
+
250
+ // API Keys
251
+ await client.apiKeys.list();
252
+ await client.apiKeys.create('CI key', { permissions: 'sending_access' });
253
+ await client.apiKeys.revoke('key_id');
254
+
255
+ // SMTP warmup
256
+ await client.smtp.warmupStatus();
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Error Handling
262
+
263
+ ```typescript
264
+ import { SendCraft, AuthenticationError, RateLimitError, NotFoundError, SendCraftError } from 'sendcraft-sdk';
265
+
266
+ try {
267
+ await client.emails.send({ to: '...', subject: '...', html: '...' });
268
+ } catch (err) {
269
+ if (err instanceof AuthenticationError) console.error('Invalid API key');
270
+ else if (err instanceof RateLimitError) console.error('Rate limited — retry later');
271
+ else if (err instanceof NotFoundError) console.error('Resource not found');
272
+ else if (err instanceof SendCraftError) console.error(`API error ${err.status}: ${err.message}`);
273
+ else throw err;
274
+ }
275
+ ```
276
+
277
+ | Class | HTTP | When |
278
+ |---|---|---|
279
+ | `AuthenticationError` | 401 | Invalid or missing API key |
280
+ | `ForbiddenError` | 403 | Insufficient permissions |
281
+ | `NotFoundError` | 404 | Resource not found |
282
+ | `RateLimitError` | 429 | Too many requests |
283
+ | `ConflictError` | 409 | Duplicate resource |
284
+ | `ValidationError` | 422 | Invalid request data |
285
+ | `ServerError` | 500 | Server error |
286
+
287
+ ---
288
+
289
+ ## TypeScript
290
+
291
+ Full TypeScript support — all options and responses are typed.
292
+
293
+ ```typescript
294
+ import type { SendEmailOptions, CreateCampaignOptions } from 'sendcraft-sdk';
295
+ ```
296
+
297
+ ---
298
+
299
+ ## CI/CD
300
+
301
+ ```yaml
302
+ # GitHub Actions
303
+ - name: Send deploy notification
304
+ env:
305
+ SENDCRAFT_API_KEY: ${{ secrets.SENDCRAFT_API_KEY }}
306
+ run: |
307
+ npx sendcraft-sdk emails.send \
308
+ --to team@myapp.com \
309
+ --subject "Deployed ${{ github.sha }}"
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Related
315
+
316
+ | Package | Description |
317
+ |---------|-------------|
318
+ | [`@sendcraft/cli`](https://www.npmjs.com/package/@sendcraft/cli) | Official CLI |
319
+ | [`sendcraft-mcp`](https://www.npmjs.com/package/sendcraft-mcp) | MCP server for AI agents |
320
+ | [`sendcraft-sdk` (PyPI)](https://pypi.org/project/sendcraft-sdk/) | Python SDK |
321
+
322
+ ---
323
+
324
+ ## License
325
+
326
+ MIT © [SendCraft](https://sendcraft.online)
@@ -0,0 +1,10 @@
1
+ export declare class HttpClient {
2
+ private axios;
3
+ constructor(apiKey: string, baseUrl: string);
4
+ get<T>(path: string, params?: Record<string, unknown>): Promise<T>;
5
+ post<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
6
+ put<T>(path: string, body?: unknown): Promise<T>;
7
+ patch<T>(path: string, body?: unknown): Promise<T>;
8
+ delete<T>(path: string, body?: unknown): Promise<T>;
9
+ private wrap;
10
+ }
package/lib/client.js ADDED
@@ -0,0 +1,96 @@
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.HttpClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const error_1 = require("./error");
9
+ class HttpClient {
10
+ constructor(apiKey, baseUrl) {
11
+ // Reject non-http(s) baseUrl schemes to prevent SSRF via javascript:/file:/data: URIs.
12
+ let parsedUrl;
13
+ try {
14
+ parsedUrl = new URL(baseUrl);
15
+ }
16
+ catch {
17
+ throw new Error(`SendCraft: invalid baseUrl — must be a valid URL, got: ${baseUrl}`);
18
+ }
19
+ if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
20
+ throw new Error(`SendCraft: invalid baseUrl scheme "${parsedUrl.protocol}" — only http: and https: are allowed`);
21
+ }
22
+ this.axios = axios_1.default.create({
23
+ baseURL: baseUrl,
24
+ headers: {
25
+ 'x-api-key': apiKey,
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ timeout: 30000,
29
+ });
30
+ }
31
+ async get(path, params) {
32
+ try {
33
+ const res = await this.axios.get(path, { params });
34
+ return res.data;
35
+ }
36
+ catch (err) {
37
+ throw this.wrap(err);
38
+ }
39
+ }
40
+ async post(path, body, extraHeaders) {
41
+ try {
42
+ const res = await this.axios.post(path, body, extraHeaders ? { headers: extraHeaders } : undefined);
43
+ return res.data;
44
+ }
45
+ catch (err) {
46
+ throw this.wrap(err);
47
+ }
48
+ }
49
+ async put(path, body) {
50
+ try {
51
+ const res = await this.axios.put(path, body);
52
+ return res.data;
53
+ }
54
+ catch (err) {
55
+ throw this.wrap(err);
56
+ }
57
+ }
58
+ async patch(path, body) {
59
+ try {
60
+ const res = await this.axios.patch(path, body);
61
+ return res.data;
62
+ }
63
+ catch (err) {
64
+ throw this.wrap(err);
65
+ }
66
+ }
67
+ async delete(path, body) {
68
+ try {
69
+ const res = await this.axios.delete(path, { data: body });
70
+ return res.data;
71
+ }
72
+ catch (err) {
73
+ throw this.wrap(err);
74
+ }
75
+ }
76
+ wrap(err) {
77
+ if (axios_1.default.isAxiosError(err)) {
78
+ const e = err;
79
+ const msg = e.response?.data?.error ?? e.response?.data?.message ?? e.message;
80
+ switch (e.response?.status) {
81
+ case 401: return new error_1.AuthError(msg);
82
+ case 403: return new error_1.ForbiddenError(msg);
83
+ case 404: return new error_1.NotFoundError(msg);
84
+ case 409: return new error_1.ConflictError(msg);
85
+ case 422: return new error_1.ValidationError(msg);
86
+ case 429: return new error_1.RateLimitError();
87
+ case 500: return new error_1.ServerError(msg);
88
+ default: return new error_1.SendCraftError(msg, e.response?.status ?? 0, 'api_error');
89
+ }
90
+ }
91
+ if (err instanceof error_1.SendCraftError)
92
+ return err;
93
+ return new error_1.SendCraftError(String(err), 0, 'unknown');
94
+ }
95
+ }
96
+ exports.HttpClient = HttpClient;
package/lib/error.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export declare class SendCraftError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ constructor(message: string, status: number, code: string);
5
+ }
6
+ export declare class AuthError extends SendCraftError {
7
+ constructor(message?: string);
8
+ }
9
+ export declare class ForbiddenError extends SendCraftError {
10
+ constructor(message?: string);
11
+ }
12
+ export declare class NotFoundError extends SendCraftError {
13
+ constructor(message?: string);
14
+ }
15
+ export declare class ConflictError extends SendCraftError {
16
+ constructor(message?: string);
17
+ }
18
+ export declare class ValidationError extends SendCraftError {
19
+ constructor(message?: string);
20
+ }
21
+ export declare class RateLimitError extends SendCraftError {
22
+ constructor(message?: string);
23
+ }
24
+ export declare class ServerError extends SendCraftError {
25
+ constructor(message?: string);
26
+ }
package/lib/error.js ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServerError = exports.RateLimitError = exports.ValidationError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.AuthError = exports.SendCraftError = void 0;
4
+ class SendCraftError extends Error {
5
+ constructor(message, status, code) {
6
+ super(message);
7
+ this.name = 'SendCraftError';
8
+ this.status = status;
9
+ this.code = code;
10
+ }
11
+ }
12
+ exports.SendCraftError = SendCraftError;
13
+ class AuthError extends SendCraftError {
14
+ constructor(message = 'Invalid or missing API key') {
15
+ super(message, 401, 'unauthorized');
16
+ this.name = 'AuthError';
17
+ }
18
+ }
19
+ exports.AuthError = AuthError;
20
+ class ForbiddenError extends SendCraftError {
21
+ constructor(message = 'You do not have permission to perform this action') {
22
+ super(message, 403, 'forbidden');
23
+ this.name = 'ForbiddenError';
24
+ }
25
+ }
26
+ exports.ForbiddenError = ForbiddenError;
27
+ class NotFoundError extends SendCraftError {
28
+ constructor(message = 'Resource not found') {
29
+ super(message, 404, 'not_found');
30
+ this.name = 'NotFoundError';
31
+ }
32
+ }
33
+ exports.NotFoundError = NotFoundError;
34
+ class ConflictError extends SendCraftError {
35
+ constructor(message = 'Resource already exists or conflicts with existing data') {
36
+ super(message, 409, 'conflict');
37
+ this.name = 'ConflictError';
38
+ }
39
+ }
40
+ exports.ConflictError = ConflictError;
41
+ class ValidationError extends SendCraftError {
42
+ constructor(message = 'Validation failed — check your request parameters') {
43
+ super(message, 422, 'validation_error');
44
+ this.name = 'ValidationError';
45
+ }
46
+ }
47
+ exports.ValidationError = ValidationError;
48
+ class RateLimitError extends SendCraftError {
49
+ constructor(message = 'Too many requests — please wait before retrying') {
50
+ super(message, 429, 'rate_limited');
51
+ this.name = 'RateLimitError';
52
+ }
53
+ }
54
+ exports.RateLimitError = RateLimitError;
55
+ class ServerError extends SendCraftError {
56
+ constructor(message = 'An internal server error occurred') {
57
+ super(message, 500, 'server_error');
58
+ this.name = 'ServerError';
59
+ }
60
+ }
61
+ exports.ServerError = ServerError;
package/lib/index.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { Emails } from './resources/emails';
2
+ import { Campaigns } from './resources/campaigns';
3
+ import { Templates } from './resources/templates';
4
+ import { Subscribers } from './resources/subscribers';
5
+ import { Analytics } from './resources/analytics';
6
+ import { Domains } from './resources/domains';
7
+ import { Webhooks } from './resources/webhooks';
8
+ import { Segments } from './resources/segments';
9
+ import { Automation } from './resources/automation';
10
+ import { Smtp } from './resources/smtp';
11
+ import { ApiKeys } from './resources/apiKeys';
12
+ export interface SendCraftConfig {
13
+ apiKey: string;
14
+ baseUrl?: string;
15
+ }
16
+ /**
17
+ * SendCraft SDK
18
+ *
19
+ * @example
20
+ * import { SendCraft } from 'sendcraft-sdk'
21
+ *
22
+ * const client = new SendCraft({ apiKey: 'sc_live_...' })
23
+ *
24
+ * // Send a transactional email
25
+ * await client.emails.send({ to: 'user@example.com', subject: 'Hello', html: '<p>Hi!</p>' })
26
+ *
27
+ * // Create and send a campaign
28
+ * const { campaign } = await client.campaigns.create({ name: 'Launch', subject: 'We are live!', from: 'hi@app.com', html: '...' })
29
+ * await client.campaigns.send(campaign._id)
30
+ *
31
+ * // Get SMTP relay credentials
32
+ * const { smtp } = await client.smtp.credentials()
33
+ *
34
+ * // Check IP warmup status
35
+ * const { warmup } = await client.smtp.warmupStatus()
36
+ *
37
+ * // List segments
38
+ * const { segments } = await client.segments.list()
39
+ *
40
+ * // Activate an automation
41
+ * await client.automation.activate('automation_id')
42
+ */
43
+ export declare class SendCraft {
44
+ readonly emails: Emails;
45
+ readonly campaigns: Campaigns;
46
+ readonly templates: Templates;
47
+ readonly subscribers: Subscribers;
48
+ readonly analytics: Analytics;
49
+ readonly domains: Domains;
50
+ readonly webhooks: Webhooks;
51
+ readonly segments: Segments;
52
+ readonly automation: Automation;
53
+ readonly smtp: Smtp;
54
+ readonly apiKeys: ApiKeys;
55
+ constructor(config: SendCraftConfig);
56
+ }
57
+ export default SendCraft;
58
+ export * from './resources/emails';
59
+ export * from './resources/campaigns';
60
+ export * from './resources/templates';
61
+ export * from './resources/subscribers';
62
+ export * from './resources/domains';
63
+ export * from './resources/webhooks';
64
+ export * from './resources/segments';
65
+ export * from './resources/automation';
66
+ export * from './resources/smtp';
67
+ export * from './resources/apiKeys';
68
+ export * from './error';