From d4567b880aa7897264341b90c5a1f6b47a3bb613 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Tue, 7 Jul 2026 22:41:31 +0200 Subject: [PATCH 1/2] Add latest Team API support --- src/api-surface.spec.ts | 90 +++++++++++++++++++++++++++++++++++++++++ src/lettermint.ts | 5 +++ src/types.ts | 37 +++++++++++------ 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/src/api-surface.spec.ts b/src/api-surface.spec.ts index 6088704..a74a57e 100644 --- a/src/api-surface.spec.ts +++ b/src/api-surface.spec.ts @@ -1,5 +1,6 @@ import { LettermintClient } from './client'; import { Lettermint } from './lettermint'; +import type * as Types from './types'; const mockFetch = jest.fn(); @@ -48,6 +49,33 @@ describe('public SDK surface', () => { expect(mockFetch.mock.calls[0][1].headers).not.toHaveProperty('x-lettermint-token'); }); + it('lists blocked file types with bearer token auth', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + extensions: ['exe'], + mime_types: ['application/x-msdownload'], + }), + text: async () => '', + } as Response); + const api = Lettermint.api('api-token'); + + await expect(api.blockedFileTypes()).resolves.toEqual({ + extensions: ['exe'], + mime_types: ['application/x-msdownload'], + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://api.lettermint.co/v1/blocked-file-types', + expect.objectContaining({ + method: 'GET', + headers: expect.objectContaining({ + Authorization: 'Bearer api-token', + }), + }) + ); + }); + it('does not let custom headers override SDK auth headers', async () => { const client = new LettermintClient({ apiToken: 'api-token', authMode: 'api' }); @@ -92,6 +120,7 @@ describe('api endpoint coverage', () => { 'domain.verifySpecificDnsRecord': 'domains.verifyDnsRecord', 'domain.updateProjects': 'domains.updateProjects', 'v1.ping': 'ping', + 'v1.blockedFileTypes': 'blockedFileTypes', 'message.index': 'messages.list', 'message.show': 'messages.retrieve', 'message.events': 'messages.events', @@ -148,3 +177,64 @@ describe('api endpoint coverage', () => { } }); }); + +describe('generated api types', () => { + it('matches current Team API schema additions', () => { + const messageEvent: Types.MessageEventType = 'auto_replied'; + const webhookEvent: Types.WebhookEvent = 'message.auto_replied'; + const volumeTier: Types.VolumeTier = 300000; + const suppression: Types.StoreSuppressionData = { + reason: 'manual', + scope: 'global', + emails: ['blocked@example.com'], + }; + const routeSettings: Types.UpdateRouteSettingsData = { + redact_email_content: true, + disable_plaintext_generation: false, + }; + const routeInboundSettings: Types.UpdateRouteInboundSettingsData = { + inbound_domain: 'inbound.example.com', + inbound_spam_threshold: 3, + attachment_delivery: 'url', + }; + const routeUpdate: Types.UpdateRouteData = { + settings: routeSettings, + inbound_settings: routeInboundSettings, + }; + const projectCreate: Types.StoreProjectData = { + name: 'Production', + short_token: true, + }; + const project: Types.ProjectData = { + id: 'project_123', + name: 'Production', + smtp_enabled: true, + redact_email_content: true, + default_route_id: null, + token_generated_at: null, + token_last_used_at: null, + token_last_used_ip: null, + created_at: '2026-06-28T00:00:00Z', + updated_at: '2026-06-28T00:00:00Z', + }; + const projectUpdate: Types.UpdateProjectData = { + redact_email_content: false, + }; + const blockedFileTypes: Types.BlockedFileTypesResponse = { + extensions: ['exe'], + mime_types: ['application/x-msdownload'], + }; + + expect({ + messageEvent, + webhookEvent, + volumeTier, + suppression, + routeUpdate, + projectCreate, + project, + projectUpdate, + blockedFileTypes, + }).toBeDefined(); + }); +}); diff --git a/src/lettermint.ts b/src/lettermint.ts index d93cc93..44507f1 100644 --- a/src/lettermint.ts +++ b/src/lettermint.ts @@ -1,4 +1,5 @@ import { LettermintClient, type LettermintClientConfig } from './client'; +import type * as Types from './types'; import { DomainsEndpoint, MessagesEndpoint, @@ -80,6 +81,10 @@ export class ApiClient { public async ping(): Promise { return (await this.client.getRaw('/ping')).trim(); } + + public async blockedFileTypes(): Promise { + return this.client.get('/blocked-file-types'); + } } export default Lettermint; diff --git a/src/types.ts b/src/types.ts index 585d6a1..b8c998a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -128,7 +128,7 @@ export interface MessageEventData { "timestamp": string; } -export type MessageEventType = "queued" | "processed" | "suppressed" | "delivered" | "soft_bounced" | "hard_bounced" | "spam_complaint" | "failed" | "blocked" | "policy_rejected" | "unsubscribed" | "opened" | "clicked" | "inbound_received" | "inbound_queued" | "inbound_spam_blocked" | "inbound_processed" | "inbound_retry"; +export type MessageEventType = "queued" | "processed" | "suppressed" | "delivered" | "auto_replied" | "soft_bounced" | "hard_bounced" | "spam_complaint" | "failed" | "blocked" | "policy_rejected" | "unsubscribed" | "opened" | "clicked" | "inbound_received" | "inbound_queued" | "inbound_spam_blocked" | "inbound_processed" | "inbound_retry"; export interface MessageListData { "id": string; @@ -165,6 +165,7 @@ export interface ProjectData { "id": string; "name": string; "smtp_enabled": boolean; + "redact_email_content": boolean; "default_route_id": string | null; "token_generated_at": string | null; "token_last_used_at": string | null; @@ -313,6 +314,7 @@ export interface StoreProjectData { "name": string; "smtp_enabled"?: boolean; "initial_routes"?: InitialRoutes; + "short_token"?: boolean; } export interface StoreRouteData { @@ -324,7 +326,7 @@ export interface StoreRouteData { export interface StoreSuppressionData { "email"?: string | null; "reason": SuppressionReason; - "scope": "team" | "project" | "route"; + "scope": SuppressionScope; "route_id"?: string | null; "project_id"?: string | null; "emails"?: string[] | null; @@ -405,6 +407,7 @@ export interface UpdateDomainProjectsData { export interface UpdateProjectData { "name"?: string | null; "smtp_enabled"?: boolean | null; + "redact_email_content"?: boolean | null; "default_route_id"?: string | null; } @@ -414,16 +417,22 @@ export interface UpdateProjectMembersData { export interface UpdateRouteData { "name"?: string | null; - "settings"?: { + "settings"?: UpdateRouteSettingsData | unknown; + "inbound_settings"?: UpdateRouteInboundSettingsData | unknown; +} + +export interface UpdateRouteInboundSettingsData { + "inbound_domain"?: string | null; + "inbound_spam_threshold"?: number | null; + "attachment_delivery"?: AttachmentDelivery | unknown; +} + +export interface UpdateRouteSettingsData { "track_opens"?: boolean | null; "track_clicks"?: boolean | null; + "disable_plaintext_generation"?: boolean | null; "disable_hosted_unsubscribe"?: boolean | null; -}; - "inbound_settings"?: { - "inbound_domain"?: string | null; - "inbound_spam_threshold"?: number | null; - "attachment_delivery"?: AttachmentDelivery; -}; + "redact_email_content"?: boolean | null; } export interface UpdateTeamData { @@ -445,7 +454,7 @@ export interface UserData { "avatar": string | null; } -export type VolumeTier = 300 | 10000 | 50000 | 125000 | 500000 | 750000 | 1000000 | 1500000; +export type VolumeTier = 300 | 10000 | 50000 | 125000 | 300000 | 500000 | 750000 | 1000000 | 1500000; export interface WebhookData { "id": string; @@ -491,7 +500,7 @@ export interface WebhookDeliveryListData { export type WebhookDeliveryStatus = "pending" | "success" | "failed" | "client_error" | "server_error" | "timeout"; -export type WebhookEvent = "message.created" | "message.sent" | "message.delivered" | "message.hard_bounced" | "message.soft_bounced" | "message.spam_complaint" | "message.failed" | "message.suppressed" | "message.unsubscribed" | "message.opened" | "message.clicked" | "message.inbound" | "message.policy_rejected" | "webhook.test"; +export type WebhookEvent = "message.created" | "message.sent" | "message.delivered" | "message.auto_replied" | "message.hard_bounced" | "message.soft_bounced" | "message.spam_complaint" | "message.failed" | "message.suppressed" | "message.unsubscribed" | "message.opened" | "message.clicked" | "message.inbound" | "message.policy_rejected" | "webhook.test"; export interface WebhookListData { "id": string; @@ -543,6 +552,10 @@ export type DomainUpdateProjectsResponse = { "data": DomainData; "message": "Domain projects updated successfully."; }; +export type BlockedFileTypesResponse = { + "extensions": string[]; + "mime_types": string[]; +}; export type MessageIndexResponse = { "data": MessageListData[]; "path": string | null; @@ -589,7 +602,7 @@ export type ProjectDestroyResponse = { export type ProjectRotateTokenResponse = { "data": ProjectData; "new_token": string; - "message": "API token rotated successfully. Please update your integrations."; + "message": "Project API token rotated successfully. Please update your integrations."; }; export type ProjectUpdateMembersRequest = UpdateProjectMembersData; export type ProjectUpdateMembersResponse = { From 10be1700c58df4640b320c8db386fb35cb288d80 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Tue, 7 Jul 2026 23:10:10 +0200 Subject: [PATCH 2/2] fix: reorder imports in `lettermint.ts` to resolve potential linting issues --- src/lettermint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lettermint.ts b/src/lettermint.ts index 44507f1..547d692 100644 --- a/src/lettermint.ts +++ b/src/lettermint.ts @@ -1,5 +1,4 @@ import { LettermintClient, type LettermintClientConfig } from './client'; -import type * as Types from './types'; import { DomainsEndpoint, MessagesEndpoint, @@ -11,6 +10,7 @@ import { WebhooksEndpoint, } from './endpoints/api'; import { EmailEndpoint } from './endpoints/email'; +import type * as Types from './types'; /** * Lettermint SDK