@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 +440 -0
- package/dist/index.d.mts +562 -0
- package/dist/index.d.ts +562 -0
- package/dist/index.js +403 -0
- package/dist/index.mjs +375 -0
- package/package.json +46 -0
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
|
+
[](https://www.npmjs.com/package/@tratto/email)
|
|
6
|
+
[](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
|