@masters-union/outbound-sdk 0.1.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/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # outbound-sdk
2
+
3
+ Official Node.js SDK for the [Outbound](https://github.com/AdarshChakrworty/outbound) email platform.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install outbound-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { OutboundClient } from 'outbound-sdk';
15
+
16
+ const outbound = new OutboundClient({
17
+ apiKey: 'mu_outbound_...', // or set OUTBOUND_API_KEY env var
18
+ baseUrl: 'https://api.your-domain.com', // or set OUTBOUND_API_URL env var
19
+ });
20
+ ```
21
+
22
+ ### Send an Email
23
+
24
+ ```ts
25
+ const { jobId, messageId } = await outbound.email.send({
26
+ toEmail: 'user@example.com',
27
+ fromEmail: 'noreply@company.com',
28
+ emailSubject: 'Welcome!',
29
+ htmlBody: '<h1>Hello World</h1>',
30
+ });
31
+ ```
32
+
33
+ ### Send Bulk Emails
34
+
35
+ ```ts
36
+ const result = await outbound.email.bulk({
37
+ fromEmail: 'noreply@company.com',
38
+ emailSubject: 'Newsletter',
39
+ emails: [
40
+ { toEmail: 'alice@example.com', htmlBody: '<h1>Hi Alice</h1>' },
41
+ { toEmail: 'bob@example.com', htmlBody: '<h1>Hi Bob</h1>' },
42
+ ],
43
+ });
44
+ // result.recipientCount, result.jobId
45
+ ```
46
+
47
+ ### Check Job Status
48
+
49
+ ```ts
50
+ const status = await outbound.email.status('job-uuid');
51
+ // status.job, status.recipients
52
+ ```
53
+
54
+ ### Templates
55
+
56
+ ```ts
57
+ // Create
58
+ const { template } = await outbound.templates.create({
59
+ name: 'welcome',
60
+ subject: 'Welcome {{firstName}}!',
61
+ htmlBody: '<h1>Hello {{firstName}}</h1>',
62
+ variables: ['firstName'],
63
+ });
64
+
65
+ // List
66
+ const { templates, total } = await outbound.templates.list({ status: 'active' });
67
+
68
+ // Send using template
69
+ const { jobId } = await outbound.templates.send({
70
+ templateId: template.id,
71
+ toEmail: 'user@example.com',
72
+ fromEmail: 'noreply@company.com',
73
+ variables: { firstName: 'John' },
74
+ });
75
+
76
+ // Bulk send using template
77
+ const bulk = await outbound.templates.bulkSend({
78
+ templateId: template.id,
79
+ fromEmail: 'noreply@company.com',
80
+ recipients: [
81
+ { toEmail: 'alice@example.com', variables: { firstName: 'Alice' } },
82
+ { toEmail: 'bob@example.com', variables: { firstName: 'Bob' } },
83
+ ],
84
+ });
85
+
86
+ // Preview
87
+ const preview = await outbound.templates.preview(template.id, {
88
+ variables: { firstName: 'John' },
89
+ });
90
+ ```
91
+
92
+ ### Suppressions
93
+
94
+ ```ts
95
+ // Add
96
+ await outbound.suppressions.add({ email: 'bad@example.com', reason: 'manual' });
97
+
98
+ // List
99
+ const { suppressions } = await outbound.suppressions.list({ reason: 'bounce' });
100
+
101
+ // Remove
102
+ await outbound.suppressions.remove('bad@example.com');
103
+ ```
104
+
105
+ ### Webhooks
106
+
107
+ ```ts
108
+ // Create
109
+ const { webhook, secret } = await outbound.webhooks.create({
110
+ url: 'https://myapp.com/webhooks/outbound',
111
+ events: ['delivery', 'bounce', 'complaint'],
112
+ });
113
+ // Store `secret` securely for signature verification
114
+
115
+ // Verify incoming webhook
116
+ const isValid = OutboundClient.verifyWebhookSignature(rawBody, signatureHeader, secret);
117
+ ```
118
+
119
+ ### Dashboard
120
+
121
+ ```ts
122
+ const dashboard = await outbound.dashboard.get();
123
+ // dashboard.last30Days.sent, dashboard.quota, etc.
124
+
125
+ const quota = await outbound.dashboard.quota();
126
+ // quota.dailyUsed, quota.monthlyUsed, quota.remaining
127
+ ```
128
+
129
+ ## Configuration
130
+
131
+ | Option | Env Variable | Default | Description |
132
+ |--------|-------------|---------|-------------|
133
+ | `apiKey` | `OUTBOUND_API_KEY` | - | Your tenant API key (required) |
134
+ | `baseUrl` | `OUTBOUND_API_URL` | `http://localhost:3000` | API base URL |
135
+ | `timeout` | - | `30000` | Request timeout in ms |
136
+ | `maxRetries` | - | `3` | Max retries on 429/5xx |
137
+ | `retryDelay` | - | `1000` | Initial retry delay in ms (exponential backoff) |
138
+
139
+ ## Error Handling
140
+
141
+ All errors extend `OutboundError` with `statusCode`, `message`, and `details`:
142
+
143
+ ```ts
144
+ import { OutboundClient, RateLimitError, NotFoundError } from 'outbound-sdk';
145
+
146
+ try {
147
+ await outbound.email.send({ ... });
148
+ } catch (err) {
149
+ if (err instanceof RateLimitError) {
150
+ console.log(`Rate limited. Retry after ${err.retryAfter}s`);
151
+ } else if (err instanceof NotFoundError) {
152
+ console.log('Resource not found');
153
+ }
154
+ }
155
+ ```
156
+
157
+ | Error Class | Status Code |
158
+ |------------|-------------|
159
+ | `BadRequestError` | 400 |
160
+ | `AuthenticationError` | 401 |
161
+ | `ForbiddenError` | 403 |
162
+ | `NotFoundError` | 404 |
163
+ | `ConflictError` | 409 |
164
+ | `RateLimitError` | 429 |
165
+ | `ServerError` | 5xx |
166
+ | `TimeoutError` | - |
167
+ | `NetworkError` | - |
168
+
169
+ ## Requirements
170
+
171
+ - Node.js 18+ (uses native `fetch`)
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,420 @@
1
+ interface OutboundConfig {
2
+ apiKey?: string;
3
+ baseUrl?: string;
4
+ timeout?: number;
5
+ maxRetries?: number;
6
+ retryDelay?: number;
7
+ }
8
+ interface ResolvedConfig {
9
+ apiKey: string;
10
+ baseUrl: string;
11
+ timeout: number;
12
+ maxRetries: number;
13
+ retryDelay: number;
14
+ }
15
+ interface SendEmailParams {
16
+ toEmail: string;
17
+ fromEmail: string;
18
+ emailSubject: string;
19
+ htmlBody: string;
20
+ ccEmail?: string;
21
+ bccEmail?: string;
22
+ senderName?: string;
23
+ replyTo?: string;
24
+ textBody?: string;
25
+ priority?: 'low' | 'normal' | 'high';
26
+ idempotencyKey?: string;
27
+ metadata?: Record<string, unknown>;
28
+ headers?: Record<string, string>;
29
+ tracking?: Record<string, unknown>;
30
+ attachments?: Attachment[];
31
+ }
32
+ interface Attachment {
33
+ filename: string;
34
+ content: string;
35
+ contentType: string;
36
+ }
37
+ interface SendEmailResponse {
38
+ message: string;
39
+ jobId: string;
40
+ messageId: string;
41
+ duplicate?: boolean;
42
+ }
43
+ interface BulkEmailParams {
44
+ fromEmail: string;
45
+ emailSubject: string;
46
+ emails: BulkEmailRecipient[];
47
+ senderName?: string;
48
+ replyTo?: string;
49
+ idempotencyKey?: string;
50
+ }
51
+ interface BulkEmailRecipient {
52
+ toEmail: string;
53
+ htmlBody: string;
54
+ subject?: string;
55
+ textBody?: string;
56
+ ccEmail?: string;
57
+ bccEmail?: string;
58
+ metadata?: Record<string, unknown>;
59
+ attachments?: Attachment[];
60
+ }
61
+ interface BulkEmailResponse {
62
+ message: string;
63
+ jobId: string;
64
+ recipientCount: number;
65
+ suppressedCount: number;
66
+ suppressedEmails: string[];
67
+ duplicatesRemoved: number;
68
+ }
69
+ interface JobStatusResponse {
70
+ job: Record<string, unknown>;
71
+ recipients: EmailRecipientStatus[];
72
+ }
73
+ interface EmailRecipientStatus {
74
+ message_id: string;
75
+ recipient_email: string;
76
+ status: EmailStatus;
77
+ ses_message_id?: string;
78
+ error_message?: string;
79
+ created_at: string;
80
+ }
81
+ type EmailStatus = 'queued' | 'processing' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'opened' | 'clicked' | 'failed';
82
+ interface CreateTemplateParams {
83
+ name: string;
84
+ subject: string;
85
+ htmlBody: string;
86
+ textBody?: string;
87
+ variables?: string[];
88
+ metadata?: Record<string, unknown>;
89
+ }
90
+ interface UpdateTemplateParams {
91
+ name?: string;
92
+ subject?: string;
93
+ htmlBody?: string;
94
+ textBody?: string;
95
+ variables?: string[];
96
+ metadata?: Record<string, unknown>;
97
+ status?: 'active' | 'archived';
98
+ }
99
+ interface ListTemplatesParams {
100
+ status?: 'active' | 'archived';
101
+ search?: string;
102
+ sortBy?: 'name' | 'createdAt' | 'use_count' | 'last_sent_at';
103
+ sortOrder?: 'ASC' | 'DESC';
104
+ page?: number;
105
+ limit?: number;
106
+ }
107
+ interface ListTemplatesResponse {
108
+ templates: Template[];
109
+ total: number;
110
+ page: number;
111
+ limit: number;
112
+ quota: {
113
+ used: number;
114
+ allocated: number;
115
+ remaining: number;
116
+ };
117
+ }
118
+ interface Template {
119
+ id: string;
120
+ tenant_id: string;
121
+ name: string;
122
+ subject: string;
123
+ html_body: string;
124
+ text_body?: string;
125
+ variables: string[];
126
+ use_count: number;
127
+ last_sent_at?: string;
128
+ ses_template_name?: string;
129
+ status: 'active' | 'archived';
130
+ metadata?: Record<string, unknown>;
131
+ created_at: string;
132
+ updated_at: string;
133
+ }
134
+ interface TemplateResponse {
135
+ message?: string;
136
+ template: Template;
137
+ }
138
+ interface TemplateSendParams {
139
+ templateId: string;
140
+ toEmail: string;
141
+ fromEmail: string;
142
+ senderName?: string;
143
+ replyTo?: string;
144
+ ccEmail?: string;
145
+ bccEmail?: string;
146
+ variables?: Record<string, string>;
147
+ priority?: 'low' | 'normal' | 'high';
148
+ idempotencyKey?: string;
149
+ metadata?: Record<string, unknown>;
150
+ headers?: Record<string, string>;
151
+ tracking?: Record<string, unknown>;
152
+ }
153
+ interface TemplateBulkSendParams {
154
+ templateId: string;
155
+ fromEmail: string;
156
+ senderName?: string;
157
+ replyTo?: string;
158
+ idempotencyKey?: string;
159
+ recipients: TemplateBulkRecipient[];
160
+ }
161
+ interface TemplateBulkRecipient {
162
+ toEmail: string;
163
+ variables?: Record<string, string>;
164
+ ccEmail?: string;
165
+ bccEmail?: string;
166
+ metadata?: Record<string, unknown>;
167
+ }
168
+ interface TemplateBulkSendResponse {
169
+ message: string;
170
+ jobId: string;
171
+ recipientCount: number;
172
+ suppressedCount: number;
173
+ suppressedEmails: string[];
174
+ duplicatesRemoved: number;
175
+ usingSesTemplate: boolean;
176
+ }
177
+ interface TemplatePreviewParams {
178
+ variables?: Record<string, string>;
179
+ }
180
+ interface TemplatePreviewResponse {
181
+ subject: string;
182
+ htmlBody: string;
183
+ textBody?: string;
184
+ missingVariables?: string[];
185
+ }
186
+ interface TemplateStatsResponse {
187
+ quota: {
188
+ used: number;
189
+ allocated: number;
190
+ remaining: number;
191
+ };
192
+ counts: {
193
+ total: number;
194
+ active: number;
195
+ archived: number;
196
+ neverUsed: number;
197
+ };
198
+ totalEmailsSentViaTemplates: number;
199
+ mostUsed: Template[];
200
+ leastUsed: Template[];
201
+ recentlyUsed: Template[];
202
+ }
203
+ interface ListSuppressionsParams {
204
+ page?: number;
205
+ limit?: number;
206
+ reason?: 'bounce' | 'complaint' | 'manual' | 'unsubscribe';
207
+ }
208
+ interface ListSuppressionsResponse {
209
+ suppressions: Suppression[];
210
+ pagination: {
211
+ page: number;
212
+ limit: number;
213
+ total: number;
214
+ };
215
+ }
216
+ interface Suppression {
217
+ id: string;
218
+ tenant_id: string;
219
+ email: string;
220
+ reason: string;
221
+ created_at: string;
222
+ }
223
+ interface AddSuppressionParams {
224
+ email: string;
225
+ reason?: 'bounce' | 'complaint' | 'manual' | 'unsubscribe';
226
+ }
227
+ interface SuppressionResponse {
228
+ suppression: Suppression;
229
+ }
230
+ type WebhookEvent = 'send' | 'delivery' | 'bounce' | 'complaint' | 'open' | 'click' | 'reject' | 'rendering_failure';
231
+ interface CreateWebhookParams {
232
+ url: string;
233
+ events: WebhookEvent[];
234
+ retryInterval?: number;
235
+ maxRetries?: number;
236
+ }
237
+ interface UpdateWebhookParams {
238
+ url?: string;
239
+ events?: WebhookEvent[];
240
+ active?: boolean;
241
+ retryInterval?: number;
242
+ maxRetries?: number;
243
+ }
244
+ interface Webhook {
245
+ id: string;
246
+ tenant_id: string;
247
+ url: string;
248
+ events: WebhookEvent[];
249
+ retry_interval: number;
250
+ max_retries: number;
251
+ active: boolean;
252
+ status: 'active' | 'defective';
253
+ created_at: string;
254
+ }
255
+ interface CreateWebhookResponse {
256
+ webhook: Webhook;
257
+ secret: string;
258
+ warning: string;
259
+ }
260
+ interface UpdateWebhookResponse {
261
+ message: string;
262
+ webhook: Webhook;
263
+ }
264
+ interface ListWebhooksResponse {
265
+ webhooks: Webhook[];
266
+ }
267
+ interface DashboardResponse {
268
+ quota: Record<string, unknown>;
269
+ templates: {
270
+ used: number;
271
+ active: number;
272
+ allocated: number;
273
+ remaining: number;
274
+ };
275
+ last30Days: {
276
+ sent: number;
277
+ delivered: number;
278
+ bounced: number;
279
+ complained: number;
280
+ opened: number;
281
+ clicked: number;
282
+ failed: number;
283
+ };
284
+ recentJobs: Record<string, unknown>[];
285
+ }
286
+ interface QuotaResponse {
287
+ used: number;
288
+ allocated: number;
289
+ remaining: number;
290
+ dailyUsed: number;
291
+ dailyLimit: number;
292
+ monthlyUsed: number;
293
+ monthlyLimit: number;
294
+ percentageUsed: number;
295
+ resetDate: string;
296
+ }
297
+
298
+ declare class HttpClient {
299
+ private config;
300
+ constructor(config: ResolvedConfig);
301
+ get<T>(path: string, params?: Record<string, unknown>): Promise<T>;
302
+ post<T>(path: string, body?: unknown): Promise<T>;
303
+ patch<T>(path: string, body?: unknown): Promise<T>;
304
+ delete<T>(path: string): Promise<T>;
305
+ private request;
306
+ private buildUrl;
307
+ private parseError;
308
+ private sleep;
309
+ }
310
+
311
+ declare class EmailResource {
312
+ private http;
313
+ constructor(http: HttpClient);
314
+ send(params: SendEmailParams): Promise<SendEmailResponse>;
315
+ bulk(params: BulkEmailParams): Promise<BulkEmailResponse>;
316
+ status(jobId: string): Promise<JobStatusResponse>;
317
+ }
318
+
319
+ declare class TemplatesResource {
320
+ private http;
321
+ constructor(http: HttpClient);
322
+ create(params: CreateTemplateParams): Promise<TemplateResponse>;
323
+ list(params?: ListTemplatesParams): Promise<ListTemplatesResponse>;
324
+ listAll(params?: Omit<ListTemplatesParams, 'page'>): AsyncGenerator<Template>;
325
+ get(id: string): Promise<TemplateResponse>;
326
+ update(id: string, params: UpdateTemplateParams): Promise<TemplateResponse>;
327
+ delete(id: string): Promise<{
328
+ message: string;
329
+ id: string;
330
+ }>;
331
+ duplicate(id: string, params?: {
332
+ name?: string;
333
+ }): Promise<TemplateResponse>;
334
+ preview(id: string, params?: TemplatePreviewParams): Promise<TemplatePreviewResponse>;
335
+ send(params: TemplateSendParams): Promise<SendEmailResponse>;
336
+ bulkSend(params: TemplateBulkSendParams): Promise<TemplateBulkSendResponse>;
337
+ stats(): Promise<TemplateStatsResponse>;
338
+ }
339
+
340
+ declare class SuppressionsResource {
341
+ private http;
342
+ constructor(http: HttpClient);
343
+ list(params?: ListSuppressionsParams): Promise<ListSuppressionsResponse>;
344
+ listAll(params?: Omit<ListSuppressionsParams, 'page'>): AsyncGenerator<Suppression>;
345
+ add(params: AddSuppressionParams): Promise<SuppressionResponse>;
346
+ remove(email: string): Promise<{
347
+ message: string;
348
+ }>;
349
+ }
350
+
351
+ declare class WebhooksResource {
352
+ private http;
353
+ constructor(http: HttpClient);
354
+ create(params: CreateWebhookParams): Promise<CreateWebhookResponse>;
355
+ list(): Promise<ListWebhooksResponse>;
356
+ update(id: string, params: UpdateWebhookParams): Promise<UpdateWebhookResponse>;
357
+ delete(id: string): Promise<{
358
+ message: string;
359
+ id: string;
360
+ }>;
361
+ }
362
+
363
+ declare class DashboardResource {
364
+ private http;
365
+ constructor(http: HttpClient);
366
+ get(): Promise<DashboardResponse>;
367
+ quota(): Promise<QuotaResponse>;
368
+ }
369
+
370
+ declare class Outbound {
371
+ readonly email: EmailResource;
372
+ readonly templates: TemplatesResource;
373
+ readonly suppressions: SuppressionsResource;
374
+ readonly webhooks: WebhooksResource;
375
+ readonly dashboard: DashboardResource;
376
+ constructor(config?: OutboundConfig);
377
+ /**
378
+ * Verify a webhook signature using HMAC-SHA256.
379
+ * Use this in your webhook handler to validate incoming requests.
380
+ */
381
+ static verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
382
+ private getEnv;
383
+ }
384
+
385
+ declare class OutboundError extends Error {
386
+ readonly statusCode: number;
387
+ readonly details?: unknown | undefined;
388
+ readonly requestId?: string | undefined;
389
+ constructor(message: string, statusCode: number, details?: unknown | undefined, requestId?: string | undefined);
390
+ }
391
+ declare class BadRequestError extends OutboundError {
392
+ constructor(message: string, details?: unknown, requestId?: string);
393
+ }
394
+ declare class AuthenticationError extends OutboundError {
395
+ constructor(message: string, details?: unknown, requestId?: string);
396
+ }
397
+ declare class ForbiddenError extends OutboundError {
398
+ constructor(message: string, details?: unknown, requestId?: string);
399
+ }
400
+ declare class NotFoundError extends OutboundError {
401
+ constructor(message: string, details?: unknown, requestId?: string);
402
+ }
403
+ declare class ConflictError extends OutboundError {
404
+ constructor(message: string, details?: unknown, requestId?: string);
405
+ }
406
+ declare class RateLimitError extends OutboundError {
407
+ readonly retryAfter?: number;
408
+ constructor(message: string, retryAfter?: number, details?: unknown, requestId?: string);
409
+ }
410
+ declare class ServerError extends OutboundError {
411
+ constructor(message: string, statusCode?: number, details?: unknown, requestId?: string);
412
+ }
413
+ declare class TimeoutError extends OutboundError {
414
+ constructor(message?: string);
415
+ }
416
+ declare class NetworkError extends OutboundError {
417
+ constructor(message?: string);
418
+ }
419
+
420
+ export { type AddSuppressionParams, type Attachment, AuthenticationError, BadRequestError, type BulkEmailParams, type BulkEmailRecipient, type BulkEmailResponse, ConflictError, type CreateTemplateParams, type CreateWebhookParams, type CreateWebhookResponse, type DashboardResponse, type EmailRecipientStatus, type EmailStatus, ForbiddenError, type JobStatusResponse, type ListSuppressionsParams, type ListSuppressionsResponse, type ListTemplatesParams, type ListTemplatesResponse, type ListWebhooksResponse, NetworkError, NotFoundError, Outbound, type OutboundConfig, OutboundError, type QuotaResponse, RateLimitError, type ResolvedConfig, type SendEmailParams, type SendEmailResponse, ServerError, type Suppression, type SuppressionResponse, type Template, type TemplateBulkRecipient, type TemplateBulkSendParams, type TemplateBulkSendResponse, type TemplatePreviewParams, type TemplatePreviewResponse, type TemplateResponse, type TemplateSendParams, type TemplateStatsResponse, TimeoutError, type UpdateTemplateParams, type UpdateWebhookParams, type UpdateWebhookResponse, type Webhook, type WebhookEvent };