@tratto/email 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,440 @@
1
+ # @tratto/email
2
+
3
+ Official Node.js SDK for the [Tratto](https://tratto.email) email platform.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@tratto/email)](https://www.npmjs.com/package/@tratto/email)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @tratto/email
12
+ # or
13
+ pnpm add @tratto/email
14
+ ```
15
+
16
+ Requires **Node.js ≥ 18** (native `fetch` support).
17
+
18
+ ---
19
+
20
+ ## Quick start
21
+
22
+ ```ts
23
+ import { Tratto } from '@tratto/email';
24
+
25
+ const tratto = new Tratto('tratto_live_...');
26
+
27
+ const { id } = await tratto.emails.send({
28
+ from: 'Acme <hello@mail.acme.com>',
29
+ to: 'user@example.com',
30
+ subject: 'Welcome!',
31
+ html: '<p>Thanks for signing up.</p>',
32
+ });
33
+
34
+ console.log('Sent email:', id);
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Setup
40
+
41
+ ### Constructor
42
+
43
+ ```ts
44
+ const tratto = new Tratto(apiKey, options?);
45
+ ```
46
+
47
+ | Parameter | Type | Description |
48
+ |---|---|---|
49
+ | `apiKey` | `string` | Required. Obtain one at `https://app.tratto.email/settings/api-keys`. Supports both `tratto_live_…` and `tratto_test_…` keys. |
50
+ | `options.baseUrl` | `string` | Optional. Defaults to `https://api.tratto.email`. |
51
+
52
+ ---
53
+
54
+ ## API Reference
55
+
56
+ All methods return `Promise<T>`. Use `async/await` or `.then()`.
57
+
58
+ ### Emails
59
+
60
+ ```ts
61
+ const { emails } = tratto;
62
+ ```
63
+
64
+ #### `emails.send(params, idempotencyKey?)`
65
+
66
+ Send a transactional email. At least one of `html`, `text`, or `templateId` is required.
67
+
68
+ ```ts
69
+ // Plain HTML
70
+ const { id } = await tratto.emails.send({
71
+ from: 'Acme <hello@mail.acme.com>',
72
+ to: 'user@example.com',
73
+ subject: 'Welcome!',
74
+ html: '<p>Hello world</p>',
75
+ });
76
+
77
+ // With a template and idempotency key
78
+ const { id } = await tratto.emails.send(
79
+ {
80
+ from: 'hello@mail.acme.com',
81
+ to: ['alice@example.com', 'bob@example.com'],
82
+ subject: 'Reset your password',
83
+ templateId: 'tpl_abc123',
84
+ variables: { name: 'Alice', link: 'https://...' },
85
+ },
86
+ 'unique-idempotency-key',
87
+ );
88
+ ```
89
+
90
+ #### `emails.list(params?)`
91
+
92
+ ```ts
93
+ const { data, pagination } = await tratto.emails.list({
94
+ status: 'delivered',
95
+ limit: 20,
96
+ });
97
+ ```
98
+
99
+ #### `emails.get(id)`
100
+
101
+ ```ts
102
+ const email = await tratto.emails.get('em_abc123');
103
+ console.log(email.events);
104
+ ```
105
+
106
+ #### `emails.listEvents(id)`
107
+
108
+ ```ts
109
+ const events = await tratto.emails.listEvents('em_abc123');
110
+ ```
111
+
112
+ ---
113
+
114
+ ### Contacts
115
+
116
+ ```ts
117
+ const { contacts } = tratto;
118
+ ```
119
+
120
+ #### `contacts.create(params)`
121
+
122
+ ```ts
123
+ const { id } = await tratto.contacts.create({
124
+ email: 'alice@example.com',
125
+ firstName: 'Alice',
126
+ tags: ['vip'],
127
+ });
128
+ ```
129
+
130
+ #### `contacts.list(params?)`
131
+
132
+ ```ts
133
+ const { data } = await tratto.contacts.list({ status: 'subscribed', limit: 50 });
134
+ ```
135
+
136
+ #### `contacts.update(id, params)`
137
+
138
+ ```ts
139
+ await tratto.contacts.update('con_abc123', { status: 'unsubscribed' });
140
+ ```
141
+
142
+ #### `contacts.importCsv(csvText)`
143
+
144
+ Bulk-import contacts from a CSV string (async job). Poll `getImportJob` to track progress.
145
+
146
+ ```ts
147
+ const csv = `email,first_name,last_name
148
+ alice@example.com,Alice,Smith
149
+ bob@example.com,Bob,Jones`;
150
+
151
+ const { jobId } = await tratto.contacts.importCsv(csv);
152
+ const status = await tratto.contacts.getImportJob(jobId);
153
+ console.log(status.processedRows);
154
+ ```
155
+
156
+ ---
157
+
158
+ ### Audiences
159
+
160
+ ```ts
161
+ // Create a dynamic segment
162
+ const { id } = await tratto.audiences.create({
163
+ name: 'Power users',
164
+ rules: [{ field: 'tags', operator: 'array_contains', value: 'vip' }],
165
+ });
166
+
167
+ // List
168
+ const { data } = await tratto.audiences.list();
169
+
170
+ // Add contacts (up to 500 IDs per call)
171
+ const result = await tratto.audiences.addContacts('aud_abc123', ['con_1', 'con_2']);
172
+ console.log(result.added);
173
+ ```
174
+
175
+ ---
176
+
177
+ ### Campaigns
178
+
179
+ ```ts
180
+ // Create a draft
181
+ const { id } = await tratto.campaigns.create({
182
+ name: 'June Newsletter',
183
+ templateId: 'tpl_abc123',
184
+ audienceId: 'aud_abc123',
185
+ fromName: 'Acme',
186
+ fromEmail: 'news@mail.acme.com',
187
+ subjectA: 'Our June update',
188
+ });
189
+
190
+ // Send immediately
191
+ const { status } = await tratto.campaigns.send(id);
192
+
193
+ // Schedule for a future date
194
+ await tratto.campaigns.send(id, { scheduledAt: new Date('2025-07-01T09:00:00Z') });
195
+
196
+ // Delivery stats
197
+ const stats = await tratto.campaigns.getStats(id);
198
+ console.log(stats.rates.openRate);
199
+
200
+ // Pause
201
+ await tratto.campaigns.pause(id);
202
+
203
+ // Test send
204
+ const { emailId } = await tratto.campaigns.testSend(id, 'me@example.com');
205
+ ```
206
+
207
+ ---
208
+
209
+ ### Templates
210
+
211
+ ```ts
212
+ // Create
213
+ const tpl = await tratto.templates.create({ name: 'Welcome email', html: '<h1>Hi {{name}}!</h1>' });
214
+
215
+ // Update (auto-creates a new version)
216
+ await tratto.templates.update(tpl.id, { html: '<h1>Hello {{name}}!</h1>' });
217
+
218
+ // Version history
219
+ const versions = await tratto.templates.listVersions(tpl.id);
220
+ const v2 = await tratto.templates.getVersion(tpl.id, 2);
221
+
222
+ // Test send
223
+ await tratto.templates.testSend(tpl.id, 'me@example.com', { name: 'Alice' });
224
+
225
+ // Delete
226
+ await tratto.templates.delete(tpl.id);
227
+ ```
228
+
229
+ ---
230
+
231
+ ### Webhooks
232
+
233
+ ```ts
234
+ // Register — save the secret, it is shown only once
235
+ const { id, secret } = await tratto.webhooks.create({
236
+ url: 'https://my-app.com/webhooks/tratto',
237
+ events: ['delivered', 'bounced', 'complained'],
238
+ });
239
+
240
+ // List
241
+ const hooks = await tratto.webhooks.list();
242
+
243
+ // Delivery history
244
+ const { data } = await tratto.webhooks.listDeliveries(id, { limit: 20 });
245
+
246
+ // Test connectivity
247
+ await tratto.webhooks.test(id);
248
+
249
+ // Rotate signing secret
250
+ const { secret: newSecret } = await tratto.webhooks.rotateSecret(id);
251
+
252
+ // Delete
253
+ await tratto.webhooks.delete(id);
254
+ ```
255
+
256
+ ---
257
+
258
+ ### Domains
259
+
260
+ ```ts
261
+ // Add domain — response contains the DNS records to publish
262
+ const domain = await tratto.domains.add('mail.acme.com');
263
+ console.log('Publish these DNS records:', domain.records);
264
+
265
+ // Trigger SPF/DKIM/DMARC verification
266
+ const verified = await tratto.domains.verify(domain.id);
267
+ console.log(verified.status);
268
+
269
+ // List / get
270
+ const { data } = await tratto.domains.list();
271
+ const detail = await tratto.domains.get(domain.id);
272
+
273
+ // Delete
274
+ const { deletedAt } = await tratto.domains.delete(domain.id);
275
+ ```
276
+
277
+ ---
278
+
279
+ ### API Keys
280
+
281
+ ```ts
282
+ // Create — raw key is shown only once
283
+ const key = await tratto.apiKeys.create({
284
+ name: 'CI deployment key',
285
+ env: 'live',
286
+ permissions: ['emails:send'],
287
+ });
288
+ console.log('Raw key (save this!):', key.key);
289
+
290
+ // List (prefix only, never raw token)
291
+ const { data } = await tratto.apiKeys.list();
292
+
293
+ // Revoke
294
+ const { revokedAt } = await tratto.apiKeys.revoke(key.id);
295
+ ```
296
+
297
+ ---
298
+
299
+ ### Analytics
300
+
301
+ ```ts
302
+ // Aggregated summary
303
+ const summary = await tratto.analytics.getSummary('30d');
304
+ console.log('Open rate:', summary.openRate);
305
+
306
+ // Daily timeseries
307
+ const points = await tratto.analytics.getTimeseries('7d');
308
+ ```
309
+
310
+ Supported periods: `'7d'` | `'30d'` | `'90d'`. Results are cached server-side for 1 hour.
311
+
312
+ ---
313
+
314
+ ### Flows
315
+
316
+ ```ts
317
+ // Create a draft flow
318
+ const { id } = await tratto.flows.create({ name: 'Welcome series' });
319
+
320
+ // Configure trigger and steps
321
+ await tratto.flows.update(id, {
322
+ trigger: { type: 'contact_joins_audience', config: { audienceId: 'aud_abc123' } },
323
+ steps: [
324
+ { id: 'step_1', type: 'send_email', config: { templateId: 'tpl_abc123' } },
325
+ { id: 'step_2', type: 'wait', config: { delay: '3d' } },
326
+ { id: 'step_3', type: 'send_email', config: { templateId: 'tpl_def456' } },
327
+ ],
328
+ });
329
+
330
+ // Activate / deactivate
331
+ await tratto.flows.activate(id);
332
+ await tratto.flows.deactivate(id);
333
+
334
+ // Delete (draft or inactive only)
335
+ await tratto.flows.delete(id);
336
+ ```
337
+
338
+ ---
339
+
340
+ ### Workspace
341
+
342
+ ```ts
343
+ // Get current workspace
344
+ const ws = await tratto.workspace.get();
345
+ console.log(ws.name, ws.plan);
346
+
347
+ // Update settings
348
+ await tratto.workspace.update({ name: 'Acme Corp', timezone: 'Europe/Rome' });
349
+
350
+ // Preferences
351
+ await tratto.workspace.updatePreferences({
352
+ locale: 'en',
353
+ emailNotifications: { weeklyReport: true },
354
+ });
355
+
356
+ // Team management
357
+ await tratto.workspace.inviteMember({ email: 'dev@acme.com', role: 'admin' });
358
+ await tratto.workspace.updateMember('usr_abc123', { role: 'member' });
359
+ await tratto.workspace.removeMember('usr_abc123');
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Error handling
365
+
366
+ Failed HTTP requests throw a `TrattoError` with `code`, `statusCode`, and an optional `docs` URL.
367
+
368
+ ```ts
369
+ import { Tratto, TrattoError } from '@tratto/email';
370
+
371
+ try {
372
+ await tratto.emails.send({ from: '...', to: '...', subject: '...', html: '...' });
373
+ } catch (err) {
374
+ if (err instanceof TrattoError) {
375
+ console.error(`[${err.statusCode}] ${err.code}: ${err.message}`);
376
+ if (err.docs) console.error('Docs:', err.docs);
377
+ } else {
378
+ throw err;
379
+ }
380
+ }
381
+ ```
382
+
383
+ ---
384
+
385
+ ## TypeScript
386
+
387
+ Full type declarations are exported:
388
+
389
+ ```ts
390
+ import type {
391
+ SendEmailParams,
392
+ EmailDetail,
393
+ EmailEvent,
394
+ PaginatedResponse,
395
+ Contact,
396
+ Audience,
397
+ Campaign,
398
+ CampaignStatsDetail,
399
+ Template,
400
+ Webhook,
401
+ Domain,
402
+ ApiKey,
403
+ ApiKeyCreated,
404
+ AnalyticsSummary,
405
+ TimeseriesPoint,
406
+ Flow,
407
+ Workspace,
408
+ WorkspaceMember,
409
+ } from '@tratto/email';
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Examples
415
+
416
+ See the [`examples/`](examples/) folder:
417
+
418
+ | File | Description |
419
+ |---|---|
420
+ | [`send-email.ts`](examples/send-email.ts) | Send transactional emails (HTML, template, with idempotency) |
421
+ | [`contacts.ts`](examples/contacts.ts) | Contact management and CSV bulk import |
422
+ | [`campaign.ts`](examples/campaign.ts) | Create, configure, and send a marketing campaign |
423
+ | [`analytics.ts`](examples/analytics.ts) | Fetch delivery metrics and daily timeseries |
424
+ | [`webhook.ts`](examples/webhook.ts) | Register a webhook and inspect delivery history |
425
+
426
+ Run any example with [tsx](https://github.com/privatenumber/tsx):
427
+
428
+ ```bash
429
+ TRATTO_API_KEY=tratto_live_... npx tsx examples/send-email.ts
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Contributing
435
+
436
+ Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
437
+
438
+ ## License
439
+
440
+ MIT