@tratto/email 0.1.0 → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tratto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -51,6 +51,45 @@ const tratto = new Tratto(apiKey, options?);
51
51
 
52
52
  ---
53
53
 
54
+ ## Test mode
55
+
56
+ Every workspace can create **test API keys** (`tratto_test_…`) alongside live ones. A test key runs the exact same pipeline — statuses, email timeline, webhooks — but **nothing is actually delivered**: no domain verification needed, no monthly quota consumed (test sends have their own daily cap).
57
+
58
+ ```ts
59
+ const tratto = new Tratto('tratto_test_...');
60
+
61
+ // Works immediately, even with an unverified sender domain
62
+ const { id, livemode } = await tratto.emails.send({
63
+ from: 'Acme <hello@any-domain.dev>',
64
+ to: 'delivered@simulator.tratto.email',
65
+ subject: 'Hello from test mode',
66
+ html: '<p>It works!</p>',
67
+ });
68
+ // livemode === false
69
+ ```
70
+
71
+ ### Simulator addresses
72
+
73
+ The recipient address picks the outcome (any other address simulates a normal delivery):
74
+
75
+ | Recipient | Outcome |
76
+ |---|---|
77
+ | `delivered@simulator.tratto.email` | `delivered` event |
78
+ | `bounced@simulator.tratto.email` | permanent bounce → email ends `failed` |
79
+ | `soft-bounced@simulator.tratto.email` | transient bounce |
80
+ | `complained@simulator.tratto.email` | spam complaint event |
81
+
82
+ ### What to expect
83
+
84
+ - Responses and webhook payloads carry `livemode: false` so your integration can tell test traffic apart.
85
+ - A test key only ever sees test data: listing or fetching emails created with a live key returns 404/empty, and vice versa.
86
+ - Test emails are retained for 7 days.
87
+ - Endpoints that reach real recipients (campaign send, template test-send, flow activation) reject test keys with `403 TEST_MODE_NOT_SUPPORTED`.
88
+
89
+ Switching to production is a one-line change: swap the key for a `tratto_live_…` one (verified sender domain required).
90
+
91
+ ---
92
+
54
93
  ## API Reference
55
94
 
56
95
  All methods return `Promise<T>`. Use `async/await` or `.then()`.
@@ -276,26 +315,6 @@ const { deletedAt } = await tratto.domains.delete(domain.id);
276
315
 
277
316
  ---
278
317
 
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
318
  ### Analytics
300
319
 
301
320
  ```ts
@@ -422,6 +441,9 @@ See the [`examples/`](examples/) folder:
422
441
  | [`campaign.ts`](examples/campaign.ts) | Create, configure, and send a marketing campaign |
423
442
  | [`analytics.ts`](examples/analytics.ts) | Fetch delivery metrics and daily timeseries |
424
443
  | [`webhook.ts`](examples/webhook.ts) | Register a webhook and inspect delivery history |
444
+ | [`nextjs.ts`](examples/nextjs.ts) | Next.js App Router route handler sending a welcome email |
445
+ | [`express.ts`](examples/express.ts) | Express route sending a password-reset email |
446
+ | [`fastify.ts`](examples/fastify.ts) | Fastify route sending an order-confirmation email via template |
425
447
 
426
448
  Run any example with [tsx](https://github.com/privatenumber/tsx):
427
449
 
package/dist/index.d.mts CHANGED
@@ -285,29 +285,6 @@ interface ListDomainsParams {
285
285
  after?: string;
286
286
  limit?: number;
287
287
  }
288
- type ApiKeyEnv = 'live' | 'test';
289
- interface ApiKey {
290
- id: string;
291
- name: string;
292
- prefix: string;
293
- env: ApiKeyEnv;
294
- permissions: string[];
295
- createdAt: string;
296
- lastUsedAt: string | null;
297
- revokedAt: string | null;
298
- }
299
- interface ApiKeyCreated extends ApiKey {
300
- key: string;
301
- }
302
- interface CreateApiKeyParams {
303
- name: string;
304
- env: ApiKeyEnv;
305
- permissions: string[];
306
- }
307
- interface ListApiKeysParams {
308
- after?: string;
309
- limit?: number;
310
- }
311
288
  type AnalyticsPeriod = '7d' | '30d' | '90d';
312
289
  interface AnalyticsSummary {
313
290
  period: AnalyticsPeriod;
@@ -503,15 +480,6 @@ declare class DomainsResource extends BaseResource {
503
480
  }>;
504
481
  }
505
482
 
506
- declare class ApiKeysResource extends BaseResource {
507
- create(params: CreateApiKeyParams, idempotencyKey?: string): Promise<ApiKeyCreated>;
508
- list(params?: ListApiKeysParams): Promise<PaginatedResponse<ApiKey>>;
509
- revoke(id: string): Promise<{
510
- id: string;
511
- revokedAt: string;
512
- }>;
513
- }
514
-
515
483
  declare class AnalyticsResource extends BaseResource {
516
484
  getSummary(period?: AnalyticsPeriod): Promise<AnalyticsSummary>;
517
485
  getTimeseries(period?: AnalyticsPeriod): Promise<TimeseriesPoint[]>;
@@ -552,11 +520,10 @@ declare class Tratto {
552
520
  readonly templates: TemplatesResource;
553
521
  readonly webhooks: WebhooksResource;
554
522
  readonly domains: DomainsResource;
555
- readonly apiKeys: ApiKeysResource;
556
523
  readonly analytics: AnalyticsResource;
557
524
  readonly flows: FlowsResource;
558
525
  readonly workspace: WorkspaceResource;
559
526
  constructor(apiKey: string, options?: TrattoOptions);
560
527
  }
561
528
 
562
- export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type ApiKey, type ApiKeyCreated, type ApiKeyEnv, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateApiKeyParams, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListApiKeysParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
529
+ export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
package/dist/index.d.ts CHANGED
@@ -285,29 +285,6 @@ interface ListDomainsParams {
285
285
  after?: string;
286
286
  limit?: number;
287
287
  }
288
- type ApiKeyEnv = 'live' | 'test';
289
- interface ApiKey {
290
- id: string;
291
- name: string;
292
- prefix: string;
293
- env: ApiKeyEnv;
294
- permissions: string[];
295
- createdAt: string;
296
- lastUsedAt: string | null;
297
- revokedAt: string | null;
298
- }
299
- interface ApiKeyCreated extends ApiKey {
300
- key: string;
301
- }
302
- interface CreateApiKeyParams {
303
- name: string;
304
- env: ApiKeyEnv;
305
- permissions: string[];
306
- }
307
- interface ListApiKeysParams {
308
- after?: string;
309
- limit?: number;
310
- }
311
288
  type AnalyticsPeriod = '7d' | '30d' | '90d';
312
289
  interface AnalyticsSummary {
313
290
  period: AnalyticsPeriod;
@@ -503,15 +480,6 @@ declare class DomainsResource extends BaseResource {
503
480
  }>;
504
481
  }
505
482
 
506
- declare class ApiKeysResource extends BaseResource {
507
- create(params: CreateApiKeyParams, idempotencyKey?: string): Promise<ApiKeyCreated>;
508
- list(params?: ListApiKeysParams): Promise<PaginatedResponse<ApiKey>>;
509
- revoke(id: string): Promise<{
510
- id: string;
511
- revokedAt: string;
512
- }>;
513
- }
514
-
515
483
  declare class AnalyticsResource extends BaseResource {
516
484
  getSummary(period?: AnalyticsPeriod): Promise<AnalyticsSummary>;
517
485
  getTimeseries(period?: AnalyticsPeriod): Promise<TimeseriesPoint[]>;
@@ -552,11 +520,10 @@ declare class Tratto {
552
520
  readonly templates: TemplatesResource;
553
521
  readonly webhooks: WebhooksResource;
554
522
  readonly domains: DomainsResource;
555
- readonly apiKeys: ApiKeysResource;
556
523
  readonly analytics: AnalyticsResource;
557
524
  readonly flows: FlowsResource;
558
525
  readonly workspace: WorkspaceResource;
559
526
  constructor(apiKey: string, options?: TrattoOptions);
560
527
  }
561
528
 
562
- export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type ApiKey, type ApiKeyCreated, type ApiKeyEnv, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateApiKeyParams, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListApiKeysParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
529
+ export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
package/dist/index.js CHANGED
@@ -288,23 +288,6 @@ var DomainsResource = class extends BaseResource {
288
288
  }
289
289
  };
290
290
 
291
- // src/resources/api-keys.ts
292
- var ApiKeysResource = class extends BaseResource {
293
- create(params, idempotencyKey) {
294
- return this.fetchData("POST", "/v1/api-keys", {
295
- body: params,
296
- headers: idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0
297
- });
298
- }
299
- list(params) {
300
- const qs = this.buildQuery({ after: params?.after, limit: params?.limit });
301
- return this.fetch("GET", `/v1/api-keys${qs}`);
302
- }
303
- revoke(id) {
304
- return this.fetchData("DELETE", `/v1/api-keys/${id}`);
305
- }
306
- };
307
-
308
291
  // src/resources/analytics.ts
309
292
  var AnalyticsResource = class extends BaseResource {
310
293
  getSummary(period = "30d") {
@@ -390,7 +373,6 @@ var Tratto = class {
390
373
  this.templates = new TemplatesResource(apiKey, baseUrl);
391
374
  this.webhooks = new WebhooksResource(apiKey, baseUrl);
392
375
  this.domains = new DomainsResource(apiKey, baseUrl);
393
- this.apiKeys = new ApiKeysResource(apiKey, baseUrl);
394
376
  this.analytics = new AnalyticsResource(apiKey, baseUrl);
395
377
  this.flows = new FlowsResource(apiKey, baseUrl);
396
378
  this.workspace = new WorkspaceResource(apiKey, baseUrl);
package/dist/index.mjs CHANGED
@@ -261,23 +261,6 @@ var DomainsResource = class extends BaseResource {
261
261
  }
262
262
  };
263
263
 
264
- // src/resources/api-keys.ts
265
- var ApiKeysResource = class extends BaseResource {
266
- create(params, idempotencyKey) {
267
- return this.fetchData("POST", "/v1/api-keys", {
268
- body: params,
269
- headers: idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0
270
- });
271
- }
272
- list(params) {
273
- const qs = this.buildQuery({ after: params?.after, limit: params?.limit });
274
- return this.fetch("GET", `/v1/api-keys${qs}`);
275
- }
276
- revoke(id) {
277
- return this.fetchData("DELETE", `/v1/api-keys/${id}`);
278
- }
279
- };
280
-
281
264
  // src/resources/analytics.ts
282
265
  var AnalyticsResource = class extends BaseResource {
283
266
  getSummary(period = "30d") {
@@ -363,7 +346,6 @@ var Tratto = class {
363
346
  this.templates = new TemplatesResource(apiKey, baseUrl);
364
347
  this.webhooks = new WebhooksResource(apiKey, baseUrl);
365
348
  this.domains = new DomainsResource(apiKey, baseUrl);
366
- this.apiKeys = new ApiKeysResource(apiKey, baseUrl);
367
349
  this.analytics = new AnalyticsResource(apiKey, baseUrl);
368
350
  this.flows = new FlowsResource(apiKey, baseUrl);
369
351
  this.workspace = new WorkspaceResource(apiKey, baseUrl);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tratto/email",
3
- "version": "0.1.0",
4
- "description": "Tratto Node.js SDK — send transactional and marketing email",
3
+ "version": "1.0.0",
4
+ "description": "Tratto Node.js SDK \u2014 send transactional and marketing email",
5
5
  "author": "Tratto <hello@tratto.email>",
6
6
  "license": "MIT",
7
7
  "homepage": "https://tratto.email",
@@ -22,7 +22,9 @@
22
22
  "require": "./dist/index.js"
23
23
  }
24
24
  },
25
- "files": ["dist"],
25
+ "files": [
26
+ "dist"
27
+ ],
26
28
  "sideEffects": false,
27
29
  "scripts": {
28
30
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
@@ -30,12 +32,17 @@
30
32
  "lint": "eslint src --ext .ts",
31
33
  "typecheck": "tsc --noEmit",
32
34
  "test": "vitest run",
33
- "test:watch": "vitest"
35
+ "test:watch": "vitest",
36
+ "test:coverage": "vitest run --coverage"
34
37
  },
35
38
  "devDependencies": {
39
+ "@types/express": "^5.0.0",
36
40
  "@typescript-eslint/eslint-plugin": "^8.0.0",
37
41
  "@typescript-eslint/parser": "^8.0.0",
42
+ "@vitest/coverage-v8": "^2.0.0",
38
43
  "eslint": "^9.0.0",
44
+ "express": "^5.0.0",
45
+ "fastify": "^5.0.0",
39
46
  "tsup": "^8.0.0",
40
47
  "typescript": "^5.5.0",
41
48
  "vitest": "^2.0.0"