@sendmailos/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 +204 -0
- package/dist/index.d.mts +480 -0
- package/dist/index.d.ts +480 -0
- package/dist/index.js +538 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +525 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +140 -0
- package/dist/react/index.d.ts +140 -0
- package/dist/react/index.js +228 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +223 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +68 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/utils/errors.ts
|
|
9
|
+
var SendMailOSError = class _SendMailOSError extends Error {
|
|
10
|
+
constructor(message, code, statusCode, retryAfter) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "SendMailOSError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.retryAfter = retryAfter;
|
|
16
|
+
if (Error.captureStackTrace) {
|
|
17
|
+
Error.captureStackTrace(this, _SendMailOSError);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Whether this error is retryable (rate limit or server error) */
|
|
21
|
+
get isRetryable() {
|
|
22
|
+
return this.statusCode === 429 || this.statusCode >= 500;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var AuthenticationError = class extends SendMailOSError {
|
|
26
|
+
constructor(message = "Invalid API key") {
|
|
27
|
+
super(message, "UNAUTHORIZED", 401);
|
|
28
|
+
this.name = "AuthenticationError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var ValidationError = class extends SendMailOSError {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message, "VALIDATION_ERROR", 400);
|
|
34
|
+
this.name = "ValidationError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var RateLimitError = class extends SendMailOSError {
|
|
38
|
+
constructor(message, retryAfter) {
|
|
39
|
+
super(message, "RATE_LIMITED", 429, retryAfter);
|
|
40
|
+
this.name = "RateLimitError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var NotFoundError = class extends SendMailOSError {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message, "NOT_FOUND", 404);
|
|
46
|
+
this.name = "NotFoundError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/resources/emails.ts
|
|
51
|
+
var EmailsResource = class {
|
|
52
|
+
constructor(request) {
|
|
53
|
+
this.request = request;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Send a transactional email
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const result = await client.emails.send({
|
|
61
|
+
* to: 'user@example.com',
|
|
62
|
+
* fromName: 'Your Company',
|
|
63
|
+
* fromEmail: 'hello@yourcompany.com',
|
|
64
|
+
* subject: 'Welcome!',
|
|
65
|
+
* html: '<h1>Hello!</h1>'
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
async send(params) {
|
|
70
|
+
return this.request("/send", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
to: params.to,
|
|
74
|
+
subject: params.subject,
|
|
75
|
+
html: params.html,
|
|
76
|
+
templateId: params.templateId,
|
|
77
|
+
from_name: params.fromName,
|
|
78
|
+
from_email: params.fromEmail,
|
|
79
|
+
variables: params.variables,
|
|
80
|
+
type: params.type || "transactional"
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Send email using a template
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const result = await client.emails.sendTemplate({
|
|
90
|
+
* to: 'user@example.com',
|
|
91
|
+
* templateId: 'tmpl_welcome',
|
|
92
|
+
* variables: { firstName: 'John' }
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
async sendTemplate(params) {
|
|
97
|
+
return this.send({
|
|
98
|
+
to: params.to,
|
|
99
|
+
subject: params.subject || "",
|
|
100
|
+
templateId: params.templateId,
|
|
101
|
+
fromName: params.fromName,
|
|
102
|
+
fromEmail: params.fromEmail,
|
|
103
|
+
variables: params.variables,
|
|
104
|
+
type: "transactional"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/resources/subscribers.ts
|
|
110
|
+
var SubscribersResource = class {
|
|
111
|
+
constructor(request) {
|
|
112
|
+
this.request = request;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create or update a subscriber (upsert)
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const { subscriber } = await client.subscribers.create({
|
|
120
|
+
* email: 'user@example.com',
|
|
121
|
+
* firstName: 'John',
|
|
122
|
+
* tags: ['newsletter', 'premium']
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
async create(params) {
|
|
127
|
+
return this.request("/subscribers", {
|
|
128
|
+
method: "POST",
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
email: params.email,
|
|
131
|
+
first_name: params.firstName,
|
|
132
|
+
last_name: params.lastName,
|
|
133
|
+
tags: params.tags,
|
|
134
|
+
domain_id: params.domainId
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* List all subscribers with pagination
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const { subscribers, total } = await client.subscribers.list({
|
|
144
|
+
* limit: 50,
|
|
145
|
+
* offset: 0
|
|
146
|
+
* });
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
async list(params = {}) {
|
|
150
|
+
const searchParams = new URLSearchParams();
|
|
151
|
+
if (params.limit) searchParams.set("limit", String(params.limit));
|
|
152
|
+
if (params.offset) searchParams.set("offset", String(params.offset));
|
|
153
|
+
const query = searchParams.toString();
|
|
154
|
+
const endpoint = query ? `/subscribers?${query}` : "/subscribers";
|
|
155
|
+
return this.request(endpoint, {
|
|
156
|
+
method: "GET"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get a single subscriber by ID
|
|
161
|
+
*/
|
|
162
|
+
async get(subscriberId) {
|
|
163
|
+
return this.request(`/subscribers/${subscriberId}`, {
|
|
164
|
+
method: "GET"
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update a subscriber by ID
|
|
169
|
+
*/
|
|
170
|
+
async update(subscriberId, params) {
|
|
171
|
+
return this.request(`/subscribers/${subscriberId}`, {
|
|
172
|
+
method: "PATCH",
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
first_name: params.firstName,
|
|
175
|
+
last_name: params.lastName,
|
|
176
|
+
tags: params.tags
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Update a subscriber by email address
|
|
182
|
+
*/
|
|
183
|
+
async updateByEmail(email, params) {
|
|
184
|
+
return this.create({
|
|
185
|
+
email,
|
|
186
|
+
...params
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Delete/unsubscribe a subscriber
|
|
191
|
+
*/
|
|
192
|
+
async delete(subscriberId) {
|
|
193
|
+
return this.request(`/subscribers/${subscriberId}`, {
|
|
194
|
+
method: "DELETE"
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Add tags to a subscriber
|
|
199
|
+
*/
|
|
200
|
+
async addTags(subscriberId, tags) {
|
|
201
|
+
return this.request(`/subscribers/${subscriberId}/tags`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
body: JSON.stringify({ tags })
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Remove tags from a subscriber
|
|
208
|
+
*/
|
|
209
|
+
async removeTags(subscriberId, tags) {
|
|
210
|
+
return this.request(`/subscribers/${subscriberId}/tags`, {
|
|
211
|
+
method: "DELETE",
|
|
212
|
+
body: JSON.stringify({ tags })
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/resources/campaigns.ts
|
|
218
|
+
var CampaignsResource = class {
|
|
219
|
+
constructor(request) {
|
|
220
|
+
this.request = request;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Send a campaign to subscribers
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const result = await client.campaigns.send({
|
|
228
|
+
* name: 'January Newsletter',
|
|
229
|
+
* subject: 'What\'s new this month',
|
|
230
|
+
* fromName: 'Your Company',
|
|
231
|
+
* fromEmail: 'news@yourcompany.com',
|
|
232
|
+
* html: '<h1>Hello {{first_name}}!</h1>',
|
|
233
|
+
* tags: ['newsletter', 'active']
|
|
234
|
+
* });
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
async send(params) {
|
|
238
|
+
return this.request("/campaigns/send", {
|
|
239
|
+
method: "POST",
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
name: params.name,
|
|
242
|
+
subject: params.subject,
|
|
243
|
+
from_name: params.fromName,
|
|
244
|
+
from_email: params.fromEmail,
|
|
245
|
+
html: params.html,
|
|
246
|
+
templateId: params.templateId,
|
|
247
|
+
tags: params.tags,
|
|
248
|
+
variables: params.variables
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Send campaign using a saved template
|
|
254
|
+
*/
|
|
255
|
+
async sendTemplate(params) {
|
|
256
|
+
return this.send({
|
|
257
|
+
name: params.name,
|
|
258
|
+
subject: params.subject,
|
|
259
|
+
fromName: params.fromName,
|
|
260
|
+
fromEmail: params.fromEmail,
|
|
261
|
+
templateId: params.templateId,
|
|
262
|
+
tags: params.tags,
|
|
263
|
+
variables: params.variables
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/resources/domains.ts
|
|
269
|
+
var DomainsResource = class {
|
|
270
|
+
constructor(request) {
|
|
271
|
+
this.request = request;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Add a new sending domain
|
|
275
|
+
* Returns DKIM tokens and DNS records for verification
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* const { domain } = await client.domains.create({
|
|
280
|
+
* domain: 'yourcompany.com'
|
|
281
|
+
* });
|
|
282
|
+
*
|
|
283
|
+
* console.log('Add these DNS records:', domain.dnsRecords);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
async create(params) {
|
|
287
|
+
return this.request("/domains", {
|
|
288
|
+
method: "POST",
|
|
289
|
+
body: JSON.stringify({
|
|
290
|
+
domain: params.domain
|
|
291
|
+
})
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* List all domains for the organization
|
|
296
|
+
*/
|
|
297
|
+
async list() {
|
|
298
|
+
return this.request("/domains", {
|
|
299
|
+
method: "GET"
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get a single domain by ID
|
|
304
|
+
*/
|
|
305
|
+
async get(domainId) {
|
|
306
|
+
return this.request(`/domains/${domainId}`, {
|
|
307
|
+
method: "GET"
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Verify a domain (trigger DNS check)
|
|
312
|
+
*/
|
|
313
|
+
async verify(domainId) {
|
|
314
|
+
return this.request(`/domains/${domainId}/verify`, {
|
|
315
|
+
method: "POST"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Delete a domain
|
|
320
|
+
*/
|
|
321
|
+
async delete(domainId) {
|
|
322
|
+
return this.request(`/domains/${domainId}`, {
|
|
323
|
+
method: "DELETE"
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/client.ts
|
|
329
|
+
var DEFAULT_BASE_URL = "https://api.sendmailos.com/api/v1";
|
|
330
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
331
|
+
var SDK_VERSION = "1.0.0";
|
|
332
|
+
var SendMailOS = class {
|
|
333
|
+
constructor(apiKey, options = {}) {
|
|
334
|
+
if (!apiKey) {
|
|
335
|
+
throw new Error("API key is required");
|
|
336
|
+
}
|
|
337
|
+
if (!apiKey.startsWith("sk_live_") && !apiKey.startsWith("sk_test_")) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
'Invalid API key format. Keys should start with "sk_live_" or "sk_test_"'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
this.apiKey = apiKey;
|
|
343
|
+
this.baseUrl = options.baseUrl?.replace(/\/$/, "") || DEFAULT_BASE_URL;
|
|
344
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
345
|
+
this.fetchFn = options.fetch || globalThis.fetch;
|
|
346
|
+
if (!this.fetchFn) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
"fetch is not available. Please provide a fetch implementation or use Node.js 18+"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const boundRequest = this.request.bind(this);
|
|
352
|
+
this.emails = new EmailsResource(boundRequest);
|
|
353
|
+
this.subscribers = new SubscribersResource(boundRequest);
|
|
354
|
+
this.campaigns = new CampaignsResource(boundRequest);
|
|
355
|
+
this.domains = new DomainsResource(boundRequest);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Make an authenticated request to the API
|
|
359
|
+
*/
|
|
360
|
+
async request(endpoint, options = {}) {
|
|
361
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
362
|
+
const controller = new AbortController();
|
|
363
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
364
|
+
try {
|
|
365
|
+
const response = await this.fetchFn(url, {
|
|
366
|
+
...options,
|
|
367
|
+
signal: controller.signal,
|
|
368
|
+
headers: {
|
|
369
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
370
|
+
"Content-Type": "application/json",
|
|
371
|
+
"User-Agent": `sendmailos-sdk/${SDK_VERSION}`,
|
|
372
|
+
"X-SDK-Version": SDK_VERSION,
|
|
373
|
+
...options.headers
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
clearTimeout(timeoutId);
|
|
377
|
+
if (response.status === 429) {
|
|
378
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") || "60", 10);
|
|
379
|
+
const errorData = await this.safeParseJson(response);
|
|
380
|
+
throw new RateLimitError(
|
|
381
|
+
errorData?.error || "Rate limit exceeded",
|
|
382
|
+
retryAfter
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (response.status === 401) {
|
|
386
|
+
throw new AuthenticationError();
|
|
387
|
+
}
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
const errorData = await this.safeParseJson(response);
|
|
390
|
+
throw new SendMailOSError(
|
|
391
|
+
errorData?.error || `Request failed with status ${response.status}`,
|
|
392
|
+
errorData?.code || "UNKNOWN_ERROR",
|
|
393
|
+
response.status
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return response.json();
|
|
397
|
+
} catch (error) {
|
|
398
|
+
clearTimeout(timeoutId);
|
|
399
|
+
if (error instanceof SendMailOSError) {
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
403
|
+
throw new SendMailOSError("Request timeout", "TIMEOUT", 408);
|
|
404
|
+
}
|
|
405
|
+
throw new SendMailOSError(
|
|
406
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
407
|
+
"NETWORK_ERROR",
|
|
408
|
+
0
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async safeParseJson(response) {
|
|
413
|
+
try {
|
|
414
|
+
return await response.json();
|
|
415
|
+
} catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Check if the API key is valid (test mode only)
|
|
421
|
+
*/
|
|
422
|
+
get isTestMode() {
|
|
423
|
+
return this.apiKey.startsWith("sk_test_");
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get the SDK version
|
|
427
|
+
*/
|
|
428
|
+
static get version() {
|
|
429
|
+
return SDK_VERSION;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/utils/webhook.ts
|
|
434
|
+
function verifyWebhookSignature(params) {
|
|
435
|
+
const { payload, signature, timestamp, secret, tolerance = 300 } = params;
|
|
436
|
+
if (!payload || !signature || !timestamp || !secret) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const timestampInt = parseInt(timestamp, 10);
|
|
440
|
+
if (isNaN(timestampInt)) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
const age = Math.floor(Date.now() / 1e3) - timestampInt;
|
|
444
|
+
if (age > tolerance) {
|
|
445
|
+
console.warn("[SendMailOS] Webhook timestamp too old:", age, "seconds");
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
if (typeof globalThis.crypto !== "undefined" && "subtle" in globalThis.crypto) {
|
|
449
|
+
return verifySync(payload, signature, timestamp, secret);
|
|
450
|
+
}
|
|
451
|
+
return verifySync(payload, signature, timestamp, secret);
|
|
452
|
+
}
|
|
453
|
+
async function verifyWebhookSignatureAsync(params) {
|
|
454
|
+
const { payload, signature, timestamp, secret, tolerance = 300 } = params;
|
|
455
|
+
if (!payload || !signature || !timestamp || !secret) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const timestampInt = parseInt(timestamp, 10);
|
|
459
|
+
if (isNaN(timestampInt)) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
const age = Math.floor(Date.now() / 1e3) - timestampInt;
|
|
463
|
+
if (age > tolerance) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const message = `${timestamp}.${payload}`;
|
|
467
|
+
const encoder = new TextEncoder();
|
|
468
|
+
const key = await crypto.subtle.importKey(
|
|
469
|
+
"raw",
|
|
470
|
+
encoder.encode(secret),
|
|
471
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
472
|
+
false,
|
|
473
|
+
["sign"]
|
|
474
|
+
);
|
|
475
|
+
const signatureBytes = await crypto.subtle.sign(
|
|
476
|
+
"HMAC",
|
|
477
|
+
key,
|
|
478
|
+
encoder.encode(message)
|
|
479
|
+
);
|
|
480
|
+
const expectedSig = bufferToHex(signatureBytes);
|
|
481
|
+
return timingSafeEqual(signature, expectedSig);
|
|
482
|
+
}
|
|
483
|
+
function verifySync(payload, signature, timestamp, secret) {
|
|
484
|
+
try {
|
|
485
|
+
const crypto2 = __require("crypto");
|
|
486
|
+
const message = `${timestamp}.${payload}`;
|
|
487
|
+
const expectedSig = crypto2.createHmac("sha256", secret).update(message).digest("hex");
|
|
488
|
+
return crypto2.timingSafeEqual(
|
|
489
|
+
Buffer.from(signature),
|
|
490
|
+
Buffer.from(expectedSig)
|
|
491
|
+
);
|
|
492
|
+
} catch {
|
|
493
|
+
console.warn("[SendMailOS] Use verifyWebhookSignatureAsync for browser/edge");
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function bufferToHex(buffer) {
|
|
498
|
+
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
499
|
+
}
|
|
500
|
+
function timingSafeEqual(a, b) {
|
|
501
|
+
if (a.length !== b.length) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
let result = 0;
|
|
505
|
+
for (let i = 0; i < a.length; i++) {
|
|
506
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
507
|
+
}
|
|
508
|
+
return result === 0;
|
|
509
|
+
}
|
|
510
|
+
function constructWebhookEvent(payload, headers, secret) {
|
|
511
|
+
const isValid = verifyWebhookSignature({
|
|
512
|
+
payload,
|
|
513
|
+
signature: headers.signature,
|
|
514
|
+
timestamp: headers.timestamp,
|
|
515
|
+
secret
|
|
516
|
+
});
|
|
517
|
+
if (!isValid) {
|
|
518
|
+
throw new Error("Invalid webhook signature");
|
|
519
|
+
}
|
|
520
|
+
return JSON.parse(payload);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export { AuthenticationError, NotFoundError, RateLimitError, SendMailOS, SendMailOSError, ValidationError, constructWebhookEvent, SendMailOS as default, verifyWebhookSignature, verifyWebhookSignatureAsync };
|
|
524
|
+
//# sourceMappingURL=index.mjs.map
|
|
525
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/errors.ts","../src/resources/emails.ts","../src/resources/subscribers.ts","../src/resources/campaigns.ts","../src/resources/domains.ts","../src/client.ts","../src/utils/webhook.ts"],"names":["crypto"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Custom error classes for SendMailOS SDK\n */\n\nexport class SendMailOSError extends Error {\n public readonly code: string;\n public readonly statusCode: number;\n public readonly retryAfter?: number;\n\n constructor(\n message: string,\n code: string,\n statusCode: number,\n retryAfter?: number\n ) {\n super(message);\n this.name = 'SendMailOSError';\n this.code = code;\n this.statusCode = statusCode;\n this.retryAfter = retryAfter;\n \n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SendMailOSError);\n }\n }\n\n /** Whether this error is retryable (rate limit or server error) */\n get isRetryable(): boolean {\n return this.statusCode === 429 || this.statusCode >= 500;\n }\n}\n\nexport class AuthenticationError extends SendMailOSError {\n constructor(message: string = 'Invalid API key') {\n super(message, 'UNAUTHORIZED', 401);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class ValidationError extends SendMailOSError {\n constructor(message: string) {\n super(message, 'VALIDATION_ERROR', 400);\n this.name = 'ValidationError';\n }\n}\n\nexport class RateLimitError extends SendMailOSError {\n constructor(message: string, retryAfter: number) {\n super(message, 'RATE_LIMITED', 429, retryAfter);\n this.name = 'RateLimitError';\n }\n}\n\nexport class NotFoundError extends SendMailOSError {\n constructor(message: string) {\n super(message, 'NOT_FOUND', 404);\n this.name = 'NotFoundError';\n }\n}\n","import type { SendEmailRequest, SendEmailResponse } from '../types';\n\n/**\n * Emails resource for sending transactional emails\n */\nexport class EmailsResource {\n constructor(\n private request: <T>(endpoint: string, options?: RequestInit) => Promise<T>\n ) {}\n\n /**\n * Send a transactional email\n * \n * @example\n * ```ts\n * const result = await client.emails.send({\n * to: 'user@example.com',\n * fromName: 'Your Company',\n * fromEmail: 'hello@yourcompany.com',\n * subject: 'Welcome!',\n * html: '<h1>Hello!</h1>'\n * });\n * ```\n */\n async send(params: SendEmailRequest): Promise<SendEmailResponse> {\n return this.request<SendEmailResponse>('/send', {\n method: 'POST',\n body: JSON.stringify({\n to: params.to,\n subject: params.subject,\n html: params.html,\n templateId: params.templateId,\n from_name: params.fromName,\n from_email: params.fromEmail,\n variables: params.variables,\n type: params.type || 'transactional',\n }),\n });\n }\n\n /**\n * Send email using a template\n * \n * @example\n * ```ts\n * const result = await client.emails.sendTemplate({\n * to: 'user@example.com',\n * templateId: 'tmpl_welcome',\n * variables: { firstName: 'John' }\n * });\n * ```\n */\n async sendTemplate(params: {\n to: string;\n templateId: string;\n fromName?: string;\n fromEmail?: string;\n subject?: string;\n variables?: Record<string, unknown>;\n }): Promise<SendEmailResponse> {\n return this.send({\n to: params.to,\n subject: params.subject || '',\n templateId: params.templateId,\n fromName: params.fromName,\n fromEmail: params.fromEmail,\n variables: params.variables,\n type: 'transactional',\n });\n }\n}\n","import type {\n Subscriber,\n CreateSubscriberRequest,\n CreateSubscriberResponse,\n ListSubscribersRequest,\n ListSubscribersResponse,\n} from '../types';\n\n/**\n * Subscribers resource for managing email contacts\n */\nexport class SubscribersResource {\n constructor(\n private request: <T>(endpoint: string, options?: RequestInit) => Promise<T>\n ) {}\n\n /**\n * Create or update a subscriber (upsert)\n * \n * @example\n * ```ts\n * const { subscriber } = await client.subscribers.create({\n * email: 'user@example.com',\n * firstName: 'John',\n * tags: ['newsletter', 'premium']\n * });\n * ```\n */\n async create(params: CreateSubscriberRequest): Promise<CreateSubscriberResponse> {\n return this.request<CreateSubscriberResponse>('/subscribers', {\n method: 'POST',\n body: JSON.stringify({\n email: params.email,\n first_name: params.firstName,\n last_name: params.lastName,\n tags: params.tags,\n domain_id: params.domainId,\n }),\n });\n }\n\n /**\n * List all subscribers with pagination\n * \n * @example\n * ```ts\n * const { subscribers, total } = await client.subscribers.list({\n * limit: 50,\n * offset: 0\n * });\n * ```\n */\n async list(params: ListSubscribersRequest = {}): Promise<ListSubscribersResponse> {\n const searchParams = new URLSearchParams();\n if (params.limit) searchParams.set('limit', String(params.limit));\n if (params.offset) searchParams.set('offset', String(params.offset));\n \n const query = searchParams.toString();\n const endpoint = query ? `/subscribers?${query}` : '/subscribers';\n \n return this.request<ListSubscribersResponse>(endpoint, {\n method: 'GET',\n });\n }\n\n /**\n * Get a single subscriber by ID\n */\n async get(subscriberId: string): Promise<{ success: boolean; subscriber: Subscriber }> {\n return this.request(`/subscribers/${subscriberId}`, {\n method: 'GET',\n });\n }\n\n /**\n * Update a subscriber by ID\n */\n async update(\n subscriberId: string,\n params: Partial<CreateSubscriberRequest>\n ): Promise<CreateSubscriberResponse> {\n return this.request<CreateSubscriberResponse>(`/subscribers/${subscriberId}`, {\n method: 'PATCH',\n body: JSON.stringify({\n first_name: params.firstName,\n last_name: params.lastName,\n tags: params.tags,\n }),\n });\n }\n\n /**\n * Update a subscriber by email address\n */\n async updateByEmail(\n email: string,\n params: Partial<Omit<CreateSubscriberRequest, 'email'>>\n ): Promise<CreateSubscriberResponse> {\n // Upsert via create endpoint\n return this.create({\n email,\n ...params,\n });\n }\n\n /**\n * Delete/unsubscribe a subscriber\n */\n async delete(subscriberId: string): Promise<{ success: boolean }> {\n return this.request(`/subscribers/${subscriberId}`, {\n method: 'DELETE',\n });\n }\n\n /**\n * Add tags to a subscriber\n */\n async addTags(subscriberId: string, tags: string[]): Promise<CreateSubscriberResponse> {\n return this.request<CreateSubscriberResponse>(`/subscribers/${subscriberId}/tags`, {\n method: 'POST',\n body: JSON.stringify({ tags }),\n });\n }\n\n /**\n * Remove tags from a subscriber\n */\n async removeTags(subscriberId: string, tags: string[]): Promise<CreateSubscriberResponse> {\n return this.request<CreateSubscriberResponse>(`/subscribers/${subscriberId}/tags`, {\n method: 'DELETE',\n body: JSON.stringify({ tags }),\n });\n }\n}\n","import type { SendCampaignRequest, SendCampaignResponse } from '../types';\n\n/**\n * Campaigns resource for bulk email sending\n */\nexport class CampaignsResource {\n constructor(\n private request: <T>(endpoint: string, options?: RequestInit) => Promise<T>\n ) {}\n\n /**\n * Send a campaign to subscribers\n * \n * @example\n * ```ts\n * const result = await client.campaigns.send({\n * name: 'January Newsletter',\n * subject: 'What\\'s new this month',\n * fromName: 'Your Company',\n * fromEmail: 'news@yourcompany.com',\n * html: '<h1>Hello {{first_name}}!</h1>',\n * tags: ['newsletter', 'active']\n * });\n * ```\n */\n async send(params: SendCampaignRequest): Promise<SendCampaignResponse> {\n return this.request<SendCampaignResponse>('/campaigns/send', {\n method: 'POST',\n body: JSON.stringify({\n name: params.name,\n subject: params.subject,\n from_name: params.fromName,\n from_email: params.fromEmail,\n html: params.html,\n templateId: params.templateId,\n tags: params.tags,\n variables: params.variables,\n }),\n });\n }\n\n /**\n * Send campaign using a saved template\n */\n async sendTemplate(params: {\n name?: string;\n templateId: string;\n subject: string;\n fromName: string;\n fromEmail: string;\n tags?: string[];\n variables?: Record<string, unknown>;\n }): Promise<SendCampaignResponse> {\n return this.send({\n name: params.name,\n subject: params.subject,\n fromName: params.fromName,\n fromEmail: params.fromEmail,\n templateId: params.templateId,\n tags: params.tags,\n variables: params.variables,\n });\n }\n}\n","import type {\n Domain,\n CreateDomainRequest,\n CreateDomainResponse,\n ListDomainsResponse,\n} from '../types';\n\n/**\n * Domains resource for managing sending domains\n */\nexport class DomainsResource {\n constructor(\n private request: <T>(endpoint: string, options?: RequestInit) => Promise<T>\n ) {}\n\n /**\n * Add a new sending domain\n * Returns DKIM tokens and DNS records for verification\n * \n * @example\n * ```ts\n * const { domain } = await client.domains.create({\n * domain: 'yourcompany.com'\n * });\n * \n * console.log('Add these DNS records:', domain.dnsRecords);\n * ```\n */\n async create(params: CreateDomainRequest): Promise<CreateDomainResponse> {\n return this.request<CreateDomainResponse>('/domains', {\n method: 'POST',\n body: JSON.stringify({\n domain: params.domain,\n }),\n });\n }\n\n /**\n * List all domains for the organization\n */\n async list(): Promise<ListDomainsResponse> {\n return this.request<ListDomainsResponse>('/domains', {\n method: 'GET',\n });\n }\n\n /**\n * Get a single domain by ID\n */\n async get(domainId: string): Promise<{ success: boolean; domain: Domain }> {\n return this.request(`/domains/${domainId}`, {\n method: 'GET',\n });\n }\n\n /**\n * Verify a domain (trigger DNS check)\n */\n async verify(domainId: string): Promise<{ success: boolean; domain: Domain }> {\n return this.request(`/domains/${domainId}/verify`, {\n method: 'POST',\n });\n }\n\n /**\n * Delete a domain\n */\n async delete(domainId: string): Promise<{ success: boolean }> {\n return this.request(`/domains/${domainId}`, {\n method: 'DELETE',\n });\n }\n}\n","import type { SendMailOSOptions, ApiError } from './types';\nimport { SendMailOSError, AuthenticationError, RateLimitError } from './utils/errors';\nimport { EmailsResource } from './resources/emails';\nimport { SubscribersResource } from './resources/subscribers';\nimport { CampaignsResource } from './resources/campaigns';\nimport { DomainsResource } from './resources/domains';\n\nconst DEFAULT_BASE_URL = 'https://api.sendmailos.com/api/v1';\nconst DEFAULT_TIMEOUT = 30000;\nconst SDK_VERSION = '1.0.0';\n\n/**\n * SendMailOS SDK Client\n * \n * @example\n * ```ts\n * import { SendMailOS } from '@sendmailos/sdk';\n * \n * const client = new SendMailOS('sk_live_your_api_key');\n * \n * // Send an email\n * await client.emails.send({\n * to: 'user@example.com',\n * subject: 'Hello!',\n * html: '<h1>Welcome!</h1>'\n * });\n * \n * // Add a subscriber\n * await client.subscribers.create({\n * email: 'user@example.com',\n * tags: ['newsletter']\n * });\n * ```\n */\nexport class SendMailOS {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly fetchFn: typeof fetch;\n\n /** Email sending operations */\n public readonly emails: EmailsResource;\n /** Subscriber management */\n public readonly subscribers: SubscribersResource;\n /** Campaign operations */\n public readonly campaigns: CampaignsResource;\n /** Domain management */\n public readonly domains: DomainsResource;\n\n constructor(apiKey: string, options: SendMailOSOptions = {}) {\n // Validate API key format\n if (!apiKey) {\n throw new Error('API key is required');\n }\n \n if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {\n throw new Error(\n 'Invalid API key format. Keys should start with \"sk_live_\" or \"sk_test_\"'\n );\n }\n\n this.apiKey = apiKey;\n this.baseUrl = options.baseUrl?.replace(/\\/$/, '') || DEFAULT_BASE_URL;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n this.fetchFn = options.fetch || globalThis.fetch;\n\n // Ensure fetch is available\n if (!this.fetchFn) {\n throw new Error(\n 'fetch is not available. Please provide a fetch implementation or use Node.js 18+'\n );\n }\n\n // Initialize resources with bound request method\n const boundRequest = this.request.bind(this);\n this.emails = new EmailsResource(boundRequest);\n this.subscribers = new SubscribersResource(boundRequest);\n this.campaigns = new CampaignsResource(boundRequest);\n this.domains = new DomainsResource(boundRequest);\n }\n\n /**\n * Make an authenticated request to the API\n */\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n \n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await this.fetchFn(url, {\n ...options,\n signal: controller.signal,\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': `sendmailos-sdk/${SDK_VERSION}`,\n 'X-SDK-Version': SDK_VERSION,\n ...options.headers,\n },\n });\n\n clearTimeout(timeoutId);\n\n // Handle rate limiting\n if (response.status === 429) {\n const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);\n const errorData = await this.safeParseJson(response);\n throw new RateLimitError(\n errorData?.error || 'Rate limit exceeded',\n retryAfter\n );\n }\n\n // Handle authentication errors\n if (response.status === 401) {\n throw new AuthenticationError();\n }\n\n // Handle other errors\n if (!response.ok) {\n const errorData = await this.safeParseJson(response) as ApiError | null;\n throw new SendMailOSError(\n errorData?.error || `Request failed with status ${response.status}`,\n errorData?.code || 'UNKNOWN_ERROR',\n response.status\n );\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n \n if (error instanceof SendMailOSError) {\n throw error;\n }\n \n if (error instanceof Error && error.name === 'AbortError') {\n throw new SendMailOSError('Request timeout', 'TIMEOUT', 408);\n }\n \n throw new SendMailOSError(\n error instanceof Error ? error.message : 'Unknown error',\n 'NETWORK_ERROR',\n 0\n );\n }\n }\n\n private async safeParseJson(response: Response): Promise<ApiError | null> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n }\n\n /**\n * Check if the API key is valid (test mode only)\n */\n get isTestMode(): boolean {\n return this.apiKey.startsWith('sk_test_');\n }\n\n /**\n * Get the SDK version\n */\n static get version(): string {\n return SDK_VERSION;\n }\n}\n","/**\n * Webhook signature verification utilities\n * \n * IMPORTANT: This module uses timing-safe comparison to prevent timing attacks.\n */\n\nexport interface VerifyWebhookParams {\n /** Raw request body as string */\n payload: string;\n /** Value of X-SendMailOS-Signature header */\n signature: string;\n /** Value of X-SendMailOS-Timestamp header */\n timestamp: string;\n /** Your webhook signing secret */\n secret: string;\n /** Maximum age in seconds (default: 300 = 5 minutes) */\n tolerance?: number;\n}\n\n/**\n * Verify a webhook signature to ensure the request came from SendMailOS\n * \n * @example\n * ```ts\n * import { verifyWebhookSignature } from '@sendmailos/sdk';\n * \n * app.post('/webhooks', (req, res) => {\n * const isValid = verifyWebhookSignature({\n * payload: JSON.stringify(req.body),\n * signature: req.headers['x-sendmailos-signature'],\n * timestamp: req.headers['x-sendmailos-timestamp'],\n * secret: process.env.WEBHOOK_SECRET\n * });\n * \n * if (!isValid) {\n * return res.status(401).json({ error: 'Invalid signature' });\n * }\n * \n * // Process webhook...\n * });\n * ```\n */\nexport function verifyWebhookSignature(params: VerifyWebhookParams): boolean {\n const { payload, signature, timestamp, secret, tolerance = 300 } = params;\n\n // Validate inputs\n if (!payload || !signature || !timestamp || !secret) {\n return false;\n }\n\n // Prevent replay attacks - check timestamp age\n const timestampInt = parseInt(timestamp, 10);\n if (isNaN(timestampInt)) {\n return false;\n }\n\n const age = Math.floor(Date.now() / 1000) - timestampInt;\n if (age > tolerance) {\n console.warn('[SendMailOS] Webhook timestamp too old:', age, 'seconds');\n return false;\n }\n\n // Node.js crypto (server-side)\n if (typeof globalThis.crypto !== 'undefined' && 'subtle' in globalThis.crypto) {\n // Use SubtleCrypto for browser/edge environments\n // Note: This is synchronous verification, use verifyWebhookSignatureAsync for async\n return verifySync(payload, signature, timestamp, secret);\n }\n\n return verifySync(payload, signature, timestamp, secret);\n}\n\n/**\n * Async version of webhook verification (recommended for edge/browser)\n */\nexport async function verifyWebhookSignatureAsync(params: VerifyWebhookParams): Promise<boolean> {\n const { payload, signature, timestamp, secret, tolerance = 300 } = params;\n\n if (!payload || !signature || !timestamp || !secret) {\n return false;\n }\n\n const timestampInt = parseInt(timestamp, 10);\n if (isNaN(timestampInt)) {\n return false;\n }\n\n const age = Math.floor(Date.now() / 1000) - timestampInt;\n if (age > tolerance) {\n return false;\n }\n\n const message = `${timestamp}.${payload}`;\n \n // Use Web Crypto API\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n \n const signatureBytes = await crypto.subtle.sign(\n 'HMAC',\n key,\n encoder.encode(message)\n );\n \n const expectedSig = bufferToHex(signatureBytes);\n \n // Timing-safe comparison\n return timingSafeEqual(signature, expectedSig);\n}\n\n// ============ Internal Helpers ============\n\nfunction verifySync(payload: string, signature: string, timestamp: string, secret: string): boolean {\n try {\n // Try Node.js crypto first\n const crypto = require('crypto');\n const message = `${timestamp}.${payload}`;\n const expectedSig = crypto\n .createHmac('sha256', secret)\n .update(message)\n .digest('hex');\n \n // Use timing-safe comparison\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSig)\n );\n } catch {\n // Fallback for non-Node environments\n console.warn('[SendMailOS] Use verifyWebhookSignatureAsync for browser/edge');\n return false;\n }\n}\n\nfunction bufferToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n \n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n\n/**\n * Construct a webhook event from raw request data\n */\nexport function constructWebhookEvent<T = unknown>(\n payload: string,\n headers: { signature: string; timestamp: string },\n secret: string\n): T {\n const isValid = verifyWebhookSignature({\n payload,\n signature: headers.signature,\n timestamp: headers.timestamp,\n secret,\n });\n\n if (!isValid) {\n throw new Error('Invalid webhook signature');\n }\n\n return JSON.parse(payload) as T;\n}\n"]}
|