@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 +21 -0
- package/README.md +326 -0
- package/lib/client.d.ts +10 -0
- package/lib/client.js +96 -0
- package/lib/error.d.ts +26 -0
- package/lib/error.js +61 -0
- package/lib/index.d.ts +68 -0
- package/lib/index.js +98 -0
- package/lib/resources/analytics.d.ts +13 -0
- package/lib/resources/analytics.js +25 -0
- package/lib/resources/apiKeys.d.ts +61 -0
- package/lib/resources/apiKeys.js +35 -0
- package/lib/resources/automation.d.ts +87 -0
- package/lib/resources/automation.js +45 -0
- package/lib/resources/campaigns.d.ts +67 -0
- package/lib/resources/campaigns.js +73 -0
- package/lib/resources/domains.d.ts +19 -0
- package/lib/resources/domains.js +33 -0
- package/lib/resources/emails.d.ts +121 -0
- package/lib/resources/emails.js +158 -0
- package/lib/resources/segments.d.ts +55 -0
- package/lib/resources/segments.js +33 -0
- package/lib/resources/smtp.d.ts +44 -0
- package/lib/resources/smtp.js +24 -0
- package/lib/resources/subscribers.d.ts +61 -0
- package/lib/resources/subscribers.js +58 -0
- package/lib/resources/templates.d.ts +34 -0
- package/lib/resources/templates.js +46 -0
- package/lib/resources/webhooks.d.ts +58 -0
- package/lib/resources/webhooks.js +94 -0
- package/package.json +55 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Emails = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Render a React Email component to an HTML string.
|
|
6
|
+
* Throws if `@react-email/render` is not installed.
|
|
7
|
+
*/
|
|
8
|
+
async function renderReact(element) {
|
|
9
|
+
let render;
|
|
10
|
+
try {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
12
|
+
const mod = require('@react-email/render');
|
|
13
|
+
render = mod.render ?? mod.default?.render;
|
|
14
|
+
if (typeof render !== 'function') {
|
|
15
|
+
throw new Error('@react-email/render does not export a `render` function — check the installed version');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
// Re-throw a helpful install message, but chain the root cause so
|
|
20
|
+
// callers can inspect it (e.g. module-not-found vs wrong-version).
|
|
21
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
22
|
+
throw new Error('[sendcraft-sdk] The `react` option requires @react-email/render.\n' +
|
|
23
|
+
'Install it: npm install @react-email/render react react-dom\n' +
|
|
24
|
+
`Root cause: ${cause}`);
|
|
25
|
+
}
|
|
26
|
+
return render(element);
|
|
27
|
+
}
|
|
28
|
+
class Emails {
|
|
29
|
+
constructor(http) {
|
|
30
|
+
this.http = http;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Send a single transactional email.
|
|
34
|
+
* @example — plain HTML
|
|
35
|
+
* await client.emails.send({ to: 'user@example.com', subject: 'Hi', html: '<p>Hello</p>' })
|
|
36
|
+
*
|
|
37
|
+
* @example — React Email component (requires @react-email/render)
|
|
38
|
+
* await client.emails.send({ to: 'user@example.com', subject: 'Hi', react: <WelcomeEmail /> })
|
|
39
|
+
*/
|
|
40
|
+
async send(options) {
|
|
41
|
+
const html = options.react ? await renderReact(options.react) : options.html;
|
|
42
|
+
if (!html)
|
|
43
|
+
throw new Error('[sendcraft-sdk] Provide either `html` or `react` in send()');
|
|
44
|
+
return this.http.post('/emails/send', {
|
|
45
|
+
toEmail: options.to,
|
|
46
|
+
toName: options.toName,
|
|
47
|
+
subject: options.subject,
|
|
48
|
+
htmlContent: html,
|
|
49
|
+
plainTextContent: options.text,
|
|
50
|
+
fromEmail: options.from,
|
|
51
|
+
fromName: options.fromName,
|
|
52
|
+
replyTo: options.replyTo,
|
|
53
|
+
cc: options.cc,
|
|
54
|
+
bcc: options.bcc,
|
|
55
|
+
}, options.idempotencyKey ? { 'X-Idempotency-Key': options.idempotencyKey } : undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render a React Email component (or return a plain HTML string) without sending.
|
|
59
|
+
* Useful for previewing templates locally or in tests.
|
|
60
|
+
* @example
|
|
61
|
+
* const html = await client.emails.render({ react: <WelcomeEmail name="Alice" /> });
|
|
62
|
+
* console.log(html); // full HTML string
|
|
63
|
+
*/
|
|
64
|
+
async render(options) {
|
|
65
|
+
if (options.react)
|
|
66
|
+
return renderReact(options.react);
|
|
67
|
+
if (options.html)
|
|
68
|
+
return options.html;
|
|
69
|
+
throw new Error('[sendcraft-sdk] Provide either `html` or `react` in render()');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Send the same email to multiple recipients.
|
|
73
|
+
* @example
|
|
74
|
+
* await client.emails.sendBulk({ emails: ['a@b.com', 'c@d.com'], subject: 'News', html: '...' })
|
|
75
|
+
*/
|
|
76
|
+
async sendBulk(options) {
|
|
77
|
+
const html = options.react ? await renderReact(options.react) : options.html;
|
|
78
|
+
if (!html)
|
|
79
|
+
throw new Error('[sendcraft-sdk] Provide either `html` or `react` in sendBulk()');
|
|
80
|
+
return this.http.post('/emails/send-bulk', {
|
|
81
|
+
emails: options.emails,
|
|
82
|
+
subject: options.subject,
|
|
83
|
+
htmlContent: html,
|
|
84
|
+
plainTextContent: options.text,
|
|
85
|
+
fromEmail: options.from,
|
|
86
|
+
fromName: options.fromName,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Schedule an email for future delivery.
|
|
91
|
+
* @example
|
|
92
|
+
* await client.emails.schedule({ to: '...', subject: '...', html: '...', scheduledAt: '2026-04-01T09:00:00Z' })
|
|
93
|
+
*/
|
|
94
|
+
async schedule(options) {
|
|
95
|
+
const html = options.react ? await renderReact(options.react) : options.html;
|
|
96
|
+
if (!html)
|
|
97
|
+
throw new Error('[sendcraft-sdk] Provide either `html` or `react` in schedule()');
|
|
98
|
+
return this.http.post('/emails/schedule', {
|
|
99
|
+
toEmail: options.to,
|
|
100
|
+
subject: options.subject,
|
|
101
|
+
htmlContent: html,
|
|
102
|
+
fromEmail: options.from,
|
|
103
|
+
scheduledTime: options.scheduledAt instanceof Date
|
|
104
|
+
? options.scheduledAt.toISOString()
|
|
105
|
+
: options.scheduledAt,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Send up to 100 distinct emails in a single API call.
|
|
110
|
+
* Each item can have a different to, subject, and html.
|
|
111
|
+
* @example
|
|
112
|
+
* await client.emails.batch([
|
|
113
|
+
* { to: 'a@example.com', subject: 'Hello A', html: '<p>Hi A</p>' },
|
|
114
|
+
* { to: 'b@example.com', subject: 'Hello B', html: '<p>Hi B</p>' },
|
|
115
|
+
* ])
|
|
116
|
+
*/
|
|
117
|
+
async batch(emails, idempotencyKey) {
|
|
118
|
+
const rendered = await Promise.all(emails.map(async (e) => {
|
|
119
|
+
const html = e.react ? await renderReact(e.react) : e.html;
|
|
120
|
+
if (!html)
|
|
121
|
+
throw new Error('[sendcraft-sdk] Each email in batch() must have `html` or `react`');
|
|
122
|
+
return {
|
|
123
|
+
toEmail: e.to,
|
|
124
|
+
toName: e.toName,
|
|
125
|
+
subject: e.subject,
|
|
126
|
+
htmlContent: html,
|
|
127
|
+
plainTextContent: e.text,
|
|
128
|
+
fromEmail: e.from,
|
|
129
|
+
fromName: e.fromName,
|
|
130
|
+
replyTo: e.replyTo,
|
|
131
|
+
};
|
|
132
|
+
}));
|
|
133
|
+
return this.http.post('/emails/batch', { emails: rendered }, idempotencyKey ? { 'X-Idempotency-Key': idempotencyKey } : undefined);
|
|
134
|
+
}
|
|
135
|
+
/** Get a single email by ID. */
|
|
136
|
+
get(id) {
|
|
137
|
+
return this.http.get(`/emails/${id}`);
|
|
138
|
+
}
|
|
139
|
+
/** Update the scheduled delivery time of a scheduled email. */
|
|
140
|
+
updateSchedule(id, scheduledAt) {
|
|
141
|
+
return this.http.patch(`/emails/${id}/schedule`, {
|
|
142
|
+
scheduledTime: scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Cancel a scheduled email before it is sent. */
|
|
146
|
+
cancelSchedule(id) {
|
|
147
|
+
return this.http.delete(`/emails/${id}/schedule`);
|
|
148
|
+
}
|
|
149
|
+
/** List sent emails with optional filters. */
|
|
150
|
+
list(options = {}) {
|
|
151
|
+
return this.http.get('/emails', options);
|
|
152
|
+
}
|
|
153
|
+
/** Get account-level email stats (open rate, click rate, etc.). */
|
|
154
|
+
stats() {
|
|
155
|
+
return this.http.get('/emails/stats/summary');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.Emails = Emails;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { HttpClient } from '../client';
|
|
2
|
+
export interface SegmentRule {
|
|
3
|
+
field: string;
|
|
4
|
+
operator: string;
|
|
5
|
+
value: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface Segment {
|
|
8
|
+
_id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
rules: SegmentRule[];
|
|
12
|
+
matchType: 'all' | 'any';
|
|
13
|
+
subscriberCount?: number;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CreateSegmentParams {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
rules: SegmentRule[];
|
|
21
|
+
matchType?: 'all' | 'any';
|
|
22
|
+
}
|
|
23
|
+
export declare class Segments {
|
|
24
|
+
private http;
|
|
25
|
+
constructor(http: HttpClient);
|
|
26
|
+
/** List all segments */
|
|
27
|
+
list(): Promise<{
|
|
28
|
+
success: boolean;
|
|
29
|
+
segments: Segment[];
|
|
30
|
+
}>;
|
|
31
|
+
/** Get a segment by ID */
|
|
32
|
+
get(id: string): Promise<{
|
|
33
|
+
success: boolean;
|
|
34
|
+
segment: Segment;
|
|
35
|
+
}>;
|
|
36
|
+
/** Create a new segment */
|
|
37
|
+
create(params: CreateSegmentParams): Promise<{
|
|
38
|
+
success: boolean;
|
|
39
|
+
segment: Segment;
|
|
40
|
+
}>;
|
|
41
|
+
/** Update a segment */
|
|
42
|
+
update(id: string, params: Partial<CreateSegmentParams>): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
segment: Segment;
|
|
45
|
+
}>;
|
|
46
|
+
/** Delete a segment */
|
|
47
|
+
delete(id: string): Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
/** Preview subscriber count matching a segment's rules */
|
|
51
|
+
preview(id: string): Promise<{
|
|
52
|
+
success: boolean;
|
|
53
|
+
count: number;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Segments = void 0;
|
|
4
|
+
class Segments {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/** List all segments */
|
|
9
|
+
list() {
|
|
10
|
+
return this.http.get('/segments');
|
|
11
|
+
}
|
|
12
|
+
/** Get a segment by ID */
|
|
13
|
+
get(id) {
|
|
14
|
+
return this.http.get(`/segments/${id}`);
|
|
15
|
+
}
|
|
16
|
+
/** Create a new segment */
|
|
17
|
+
create(params) {
|
|
18
|
+
return this.http.post('/segments', params);
|
|
19
|
+
}
|
|
20
|
+
/** Update a segment */
|
|
21
|
+
update(id, params) {
|
|
22
|
+
return this.http.put(`/segments/${id}`, params);
|
|
23
|
+
}
|
|
24
|
+
/** Delete a segment */
|
|
25
|
+
delete(id) {
|
|
26
|
+
return this.http.delete(`/segments/${id}`);
|
|
27
|
+
}
|
|
28
|
+
/** Preview subscriber count matching a segment's rules */
|
|
29
|
+
preview(id) {
|
|
30
|
+
return this.http.get(`/segments/${id}/preview`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.Segments = Segments;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HttpClient } from '../client';
|
|
2
|
+
export interface SmtpCredentials {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
encryption: string;
|
|
6
|
+
username: string;
|
|
7
|
+
passwordHint: string;
|
|
8
|
+
instructions: {
|
|
9
|
+
note: string;
|
|
10
|
+
apiKeyLocation: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface SmtpWarmupStatus {
|
|
14
|
+
warmupDay: number;
|
|
15
|
+
dailyLimit: number | null;
|
|
16
|
+
todayCount: number;
|
|
17
|
+
remainingToday: number | null;
|
|
18
|
+
isWarmedUp: boolean;
|
|
19
|
+
percentComplete: number;
|
|
20
|
+
warmupStartDate: string;
|
|
21
|
+
totalSent: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class Smtp {
|
|
24
|
+
private http;
|
|
25
|
+
constructor(http: HttpClient);
|
|
26
|
+
/**
|
|
27
|
+
* Get SMTP relay credentials for your account.
|
|
28
|
+
* Use the returned host/port/username + your API key as the SMTP password
|
|
29
|
+
* in any nodemailer, smtplib, or PHPMailer integration.
|
|
30
|
+
*/
|
|
31
|
+
credentials(): Promise<{
|
|
32
|
+
success: boolean;
|
|
33
|
+
smtp: SmtpCredentials;
|
|
34
|
+
examples: Record<string, string>;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Get current IP warmup status for the self-hosted SMTP server.
|
|
38
|
+
* Shows daily limit, emails sent today, and warmup progress.
|
|
39
|
+
*/
|
|
40
|
+
warmupStatus(): Promise<{
|
|
41
|
+
success: boolean;
|
|
42
|
+
warmup: SmtpWarmupStatus;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Smtp = void 0;
|
|
4
|
+
class Smtp {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get SMTP relay credentials for your account.
|
|
10
|
+
* Use the returned host/port/username + your API key as the SMTP password
|
|
11
|
+
* in any nodemailer, smtplib, or PHPMailer integration.
|
|
12
|
+
*/
|
|
13
|
+
credentials() {
|
|
14
|
+
return this.http.get('/smtp/credentials');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get current IP warmup status for the self-hosted SMTP server.
|
|
18
|
+
* Shows daily limit, emails sent today, and warmup progress.
|
|
19
|
+
*/
|
|
20
|
+
warmupStatus() {
|
|
21
|
+
return this.http.get('/smtp/warmup');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.Smtp = Smtp;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HttpClient } from '../client';
|
|
2
|
+
export interface AddSubscriberOptions {
|
|
3
|
+
email: string;
|
|
4
|
+
listId: string;
|
|
5
|
+
firstName?: string;
|
|
6
|
+
lastName?: string;
|
|
7
|
+
customFields?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface ImportSubscribersOptions {
|
|
10
|
+
listId: string;
|
|
11
|
+
subscribers: Array<{
|
|
12
|
+
email: string;
|
|
13
|
+
firstName?: string;
|
|
14
|
+
lastName?: string;
|
|
15
|
+
phone?: string;
|
|
16
|
+
customFields?: Record<string, unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export interface ListSubscribersOptions {
|
|
20
|
+
page?: number;
|
|
21
|
+
limit?: number;
|
|
22
|
+
status?: 'active' | 'unsubscribed' | 'bounced';
|
|
23
|
+
}
|
|
24
|
+
export interface UpdateSubscriberOptions {
|
|
25
|
+
firstName?: string;
|
|
26
|
+
lastName?: string;
|
|
27
|
+
customFields?: Record<string, unknown>;
|
|
28
|
+
emailPreferences?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export declare class Subscribers {
|
|
31
|
+
private http;
|
|
32
|
+
constructor(http: HttpClient);
|
|
33
|
+
/**
|
|
34
|
+
* Add a single subscriber to a list.
|
|
35
|
+
* @example
|
|
36
|
+
* await client.subscribers.add({ email: 'user@example.com', listId: 'list_id' })
|
|
37
|
+
*/
|
|
38
|
+
add(options: AddSubscriberOptions): Promise<unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Bulk import up to 10,000 subscribers into a list.
|
|
41
|
+
* @example
|
|
42
|
+
* await client.subscribers.import({ listId: '...', subscribers: [{ email: 'a@b.com' }, { email: 'c@d.com' }] })
|
|
43
|
+
*/
|
|
44
|
+
import(options: ImportSubscribersOptions): Promise<unknown>;
|
|
45
|
+
/** List all subscribers across all your lists. */
|
|
46
|
+
list(options?: ListSubscribersOptions): Promise<unknown>;
|
|
47
|
+
/** Get subscribers for a specific list. */
|
|
48
|
+
listByList(listId: string, options?: {
|
|
49
|
+
status?: string;
|
|
50
|
+
limit?: number;
|
|
51
|
+
skip?: number;
|
|
52
|
+
}): Promise<unknown>;
|
|
53
|
+
/** Update subscriber details. */
|
|
54
|
+
update(subscriberId: string, options: UpdateSubscriberOptions): Promise<unknown>;
|
|
55
|
+
/** Soft-delete (archive) a subscriber. */
|
|
56
|
+
delete(subscriberId: string): Promise<unknown>;
|
|
57
|
+
/** Get a subscriber's email preferences. */
|
|
58
|
+
preferences(subscriberId: string): Promise<unknown>;
|
|
59
|
+
/** Update a subscriber's email preferences. */
|
|
60
|
+
updatePreferences(subscriberId: string, emailPreferences: Record<string, unknown>): Promise<unknown>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Subscribers = void 0;
|
|
4
|
+
class Subscribers {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Add a single subscriber to a list.
|
|
10
|
+
* @example
|
|
11
|
+
* await client.subscribers.add({ email: 'user@example.com', listId: 'list_id' })
|
|
12
|
+
*/
|
|
13
|
+
add(options) {
|
|
14
|
+
return this.http.post('/subscribers/add', {
|
|
15
|
+
email: options.email,
|
|
16
|
+
listId: options.listId,
|
|
17
|
+
firstName: options.firstName,
|
|
18
|
+
lastName: options.lastName,
|
|
19
|
+
customFields: options.customFields,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Bulk import up to 10,000 subscribers into a list.
|
|
24
|
+
* @example
|
|
25
|
+
* await client.subscribers.import({ listId: '...', subscribers: [{ email: 'a@b.com' }, { email: 'c@d.com' }] })
|
|
26
|
+
*/
|
|
27
|
+
import(options) {
|
|
28
|
+
return this.http.post('/subscribers/import', {
|
|
29
|
+
listId: options.listId,
|
|
30
|
+
subscribers: options.subscribers,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** List all subscribers across all your lists. */
|
|
34
|
+
list(options = {}) {
|
|
35
|
+
return this.http.get('/subscribers', options);
|
|
36
|
+
}
|
|
37
|
+
/** Get subscribers for a specific list. */
|
|
38
|
+
listByList(listId, options = {}) {
|
|
39
|
+
return this.http.get(`/subscribers/list/${listId}`, options);
|
|
40
|
+
}
|
|
41
|
+
/** Update subscriber details. */
|
|
42
|
+
update(subscriberId, options) {
|
|
43
|
+
return this.http.put(`/subscribers/${subscriberId}`, options);
|
|
44
|
+
}
|
|
45
|
+
/** Soft-delete (archive) a subscriber. */
|
|
46
|
+
delete(subscriberId) {
|
|
47
|
+
return this.http.delete(`/subscribers/${subscriberId}`);
|
|
48
|
+
}
|
|
49
|
+
/** Get a subscriber's email preferences. */
|
|
50
|
+
preferences(subscriberId) {
|
|
51
|
+
return this.http.get(`/subscribers/preferences/${subscriberId}`);
|
|
52
|
+
}
|
|
53
|
+
/** Update a subscriber's email preferences. */
|
|
54
|
+
updatePreferences(subscriberId, emailPreferences) {
|
|
55
|
+
return this.http.put(`/subscribers/${subscriberId}/preferences`, { emailPreferences });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.Subscribers = Subscribers;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { HttpClient } from '../client';
|
|
2
|
+
export interface CreateTemplateOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
subject: string;
|
|
5
|
+
html: string;
|
|
6
|
+
category?: string;
|
|
7
|
+
variables?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface UpdateTemplateOptions {
|
|
10
|
+
name?: string;
|
|
11
|
+
subject?: string;
|
|
12
|
+
html?: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
previewText?: string;
|
|
15
|
+
category?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class Templates {
|
|
18
|
+
private http;
|
|
19
|
+
constructor(http: HttpClient);
|
|
20
|
+
/**
|
|
21
|
+
* Create a reusable email template.
|
|
22
|
+
* @example
|
|
23
|
+
* await client.templates.create({ name: 'Welcome', subject: 'Welcome to {{appName}}', html: '<p>Hi {{firstName}}</p>' })
|
|
24
|
+
*/
|
|
25
|
+
create(options: CreateTemplateOptions): Promise<unknown>;
|
|
26
|
+
/** List all templates. */
|
|
27
|
+
list(): Promise<unknown>;
|
|
28
|
+
/** Get a template by ID. */
|
|
29
|
+
get(templateId: string): Promise<unknown>;
|
|
30
|
+
/** Update a template. */
|
|
31
|
+
update(templateId: string, options: UpdateTemplateOptions): Promise<unknown>;
|
|
32
|
+
/** Delete a template. */
|
|
33
|
+
delete(templateId: string): Promise<unknown>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Templates = void 0;
|
|
4
|
+
class Templates {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a reusable email template.
|
|
10
|
+
* @example
|
|
11
|
+
* await client.templates.create({ name: 'Welcome', subject: 'Welcome to {{appName}}', html: '<p>Hi {{firstName}}</p>' })
|
|
12
|
+
*/
|
|
13
|
+
create(options) {
|
|
14
|
+
return this.http.post('/templates', {
|
|
15
|
+
name: options.name,
|
|
16
|
+
subject: options.subject,
|
|
17
|
+
htmlContent: options.html,
|
|
18
|
+
category: options.category,
|
|
19
|
+
variables: options.variables,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** List all templates. */
|
|
23
|
+
list() {
|
|
24
|
+
return this.http.get('/templates');
|
|
25
|
+
}
|
|
26
|
+
/** Get a template by ID. */
|
|
27
|
+
get(templateId) {
|
|
28
|
+
return this.http.get(`/templates/${templateId}`);
|
|
29
|
+
}
|
|
30
|
+
/** Update a template. */
|
|
31
|
+
update(templateId, options) {
|
|
32
|
+
return this.http.put(`/templates/${templateId}`, {
|
|
33
|
+
name: options.name,
|
|
34
|
+
subject: options.subject,
|
|
35
|
+
htmlContent: options.html,
|
|
36
|
+
plainTextContent: options.text,
|
|
37
|
+
previewText: options.previewText,
|
|
38
|
+
category: options.category,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Delete a template. */
|
|
42
|
+
delete(templateId) {
|
|
43
|
+
return this.http.delete(`/templates/${templateId}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.Templates = Templates;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { HttpClient } from '../client';
|
|
2
|
+
export interface CreateWebhookOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
events: string[];
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WebhookEvent {
|
|
8
|
+
type: string;
|
|
9
|
+
messageId: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare class Webhooks {
|
|
14
|
+
private http;
|
|
15
|
+
constructor(http: HttpClient);
|
|
16
|
+
/**
|
|
17
|
+
* Register a webhook endpoint.
|
|
18
|
+
* @example
|
|
19
|
+
* await client.webhooks.create({ url: 'https://myapp.com/hooks/email', events: ['email.bounced', 'email.opened'] })
|
|
20
|
+
*/
|
|
21
|
+
create(options: CreateWebhookOptions): Promise<unknown>;
|
|
22
|
+
/** List all registered webhooks. */
|
|
23
|
+
list(): Promise<unknown>;
|
|
24
|
+
/** Delete a webhook by ID. */
|
|
25
|
+
delete(webhookId: string): Promise<unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Verify the HMAC-SHA256 signature of an incoming webhook request.
|
|
28
|
+
* Use this in your webhook handler to confirm the request came from SendCraft.
|
|
29
|
+
*
|
|
30
|
+
* @param payload - Raw request body string (before JSON.parse)
|
|
31
|
+
* @param signature - Value of the `x-sendcraft-signature` header
|
|
32
|
+
* @param secret - Your webhook signing secret from the SendCraft dashboard
|
|
33
|
+
* @returns `true` if the signature is valid
|
|
34
|
+
*
|
|
35
|
+
* @example — Express
|
|
36
|
+
* app.post('/hooks/sendcraft', express.raw({ type: 'application/json' }), (req, res) => {
|
|
37
|
+
* const valid = client.webhooks.verify(
|
|
38
|
+
* req.body.toString(),
|
|
39
|
+
* req.headers['x-sendcraft-signature'] as string,
|
|
40
|
+
* process.env.SENDCRAFT_WEBHOOK_SECRET!,
|
|
41
|
+
* );
|
|
42
|
+
* if (!valid) return res.sendStatus(401);
|
|
43
|
+
* const event = JSON.parse(req.body.toString()) as WebhookEvent;
|
|
44
|
+
* // handle event...
|
|
45
|
+
* res.sendStatus(200);
|
|
46
|
+
* });
|
|
47
|
+
*/
|
|
48
|
+
verify(payload: string, signature: string, secret: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Parse and type a raw webhook payload string.
|
|
51
|
+
* Throws a descriptive error on malformed JSON rather than an opaque
|
|
52
|
+
* SyntaxError from the runtime.
|
|
53
|
+
* @example
|
|
54
|
+
* const event = client.webhooks.parse(req.body.toString());
|
|
55
|
+
* if (event.type === 'email.bounced') { ... }
|
|
56
|
+
*/
|
|
57
|
+
parse(payload: string): WebhookEvent;
|
|
58
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Webhooks = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
class Webhooks {
|
|
6
|
+
constructor(http) {
|
|
7
|
+
this.http = http;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Register a webhook endpoint.
|
|
11
|
+
* @example
|
|
12
|
+
* await client.webhooks.create({ url: 'https://myapp.com/hooks/email', events: ['email.bounced', 'email.opened'] })
|
|
13
|
+
*/
|
|
14
|
+
create(options) {
|
|
15
|
+
return this.http.post('/webhooks', options);
|
|
16
|
+
}
|
|
17
|
+
/** List all registered webhooks. */
|
|
18
|
+
list() {
|
|
19
|
+
return this.http.get('/webhooks');
|
|
20
|
+
}
|
|
21
|
+
/** Delete a webhook by ID. */
|
|
22
|
+
delete(webhookId) {
|
|
23
|
+
return this.http.delete(`/webhooks/${webhookId}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Verify the HMAC-SHA256 signature of an incoming webhook request.
|
|
27
|
+
* Use this in your webhook handler to confirm the request came from SendCraft.
|
|
28
|
+
*
|
|
29
|
+
* @param payload - Raw request body string (before JSON.parse)
|
|
30
|
+
* @param signature - Value of the `x-sendcraft-signature` header
|
|
31
|
+
* @param secret - Your webhook signing secret from the SendCraft dashboard
|
|
32
|
+
* @returns `true` if the signature is valid
|
|
33
|
+
*
|
|
34
|
+
* @example — Express
|
|
35
|
+
* app.post('/hooks/sendcraft', express.raw({ type: 'application/json' }), (req, res) => {
|
|
36
|
+
* const valid = client.webhooks.verify(
|
|
37
|
+
* req.body.toString(),
|
|
38
|
+
* req.headers['x-sendcraft-signature'] as string,
|
|
39
|
+
* process.env.SENDCRAFT_WEBHOOK_SECRET!,
|
|
40
|
+
* );
|
|
41
|
+
* if (!valid) return res.sendStatus(401);
|
|
42
|
+
* const event = JSON.parse(req.body.toString()) as WebhookEvent;
|
|
43
|
+
* // handle event...
|
|
44
|
+
* res.sendStatus(200);
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
verify(payload, signature, secret) {
|
|
48
|
+
if (!payload || !signature || !secret)
|
|
49
|
+
return false;
|
|
50
|
+
const expected = (0, crypto_1.createHmac)('sha256', secret).update(payload).digest('hex');
|
|
51
|
+
// Use Node's built-in constant-time comparison — avoids manual loop and
|
|
52
|
+
// the early-exit length check that leaks whether the provided signature
|
|
53
|
+
// is the right length (expected is always 64 hex chars).
|
|
54
|
+
try {
|
|
55
|
+
return (0, crypto_1.timingSafeEqual)(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Buffer.from() throws if signature is not valid hex; treat as invalid.
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse and type a raw webhook payload string.
|
|
64
|
+
* Throws a descriptive error on malformed JSON rather than an opaque
|
|
65
|
+
* SyntaxError from the runtime.
|
|
66
|
+
* @example
|
|
67
|
+
* const event = client.webhooks.parse(req.body.toString());
|
|
68
|
+
* if (event.type === 'email.bounced') { ... }
|
|
69
|
+
*/
|
|
70
|
+
parse(payload) {
|
|
71
|
+
if (typeof payload !== 'string' || payload.length === 0) {
|
|
72
|
+
throw new Error('[sendcraft-sdk] webhooks.parse() received an empty payload');
|
|
73
|
+
}
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(payload);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error('[sendcraft-sdk] webhooks.parse() received invalid JSON');
|
|
80
|
+
}
|
|
81
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
82
|
+
throw new Error('[sendcraft-sdk] webhooks.parse() expected a JSON object');
|
|
83
|
+
}
|
|
84
|
+
// Guard against prototype pollution: __proto__ / constructor overrides
|
|
85
|
+
const obj = parsed;
|
|
86
|
+
if (Object.prototype.hasOwnProperty.call(obj, '__proto__') ||
|
|
87
|
+
Object.prototype.hasOwnProperty.call(obj, 'constructor') ||
|
|
88
|
+
Object.prototype.hasOwnProperty.call(obj, 'prototype')) {
|
|
89
|
+
throw new Error('[sendcraft-sdk] webhooks.parse() rejected payload with forbidden keys');
|
|
90
|
+
}
|
|
91
|
+
return obj;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.Webhooks = Webhooks;
|