Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/api-surface.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ describe('public SDK surface', () => {
expect.objectContaining({ method: 'PUT', body: JSON.stringify(assignment) })
);
});

it('reschedules and cancels scheduled messages', async () => {
const api = Lettermint.api('api-token');
await api.messages.reschedule('message/id', { scheduled_at: '2026-08-27T09:00:00Z' });
await api.messages.cancel('message/id');

expect(mockFetch).toHaveBeenNthCalledWith(
1,
'https://api.lettermint.co/v1/messages/message%2Fid',
expect.objectContaining({
method: 'PATCH',
body: JSON.stringify({ scheduled_at: '2026-08-27T09:00:00Z' }),
})
);
expect(mockFetch).toHaveBeenNthCalledWith(
2,
'https://api.lettermint.co/v1/messages/message%2Fid/cancel',
expect.objectContaining({ method: 'POST' })
);
});
});

describe('api endpoint coverage', () => {
Expand All @@ -151,6 +171,8 @@ describe('api endpoint coverage', () => {
'v1.blockedFileTypes': 'blockedFileTypes',
'message.index': 'messages.list',
'message.show': 'messages.retrieve',
rescheduleMessage: 'messages.reschedule',
cancelScheduledMessage: 'messages.cancel',
'message.events': 'messages.events',
'message.source': 'messages.source',
'message.html': 'messages.html',
Expand Down Expand Up @@ -208,7 +230,7 @@ describe('api endpoint coverage', () => {

describe('generated api types', () => {
it('matches current Team API schema additions', () => {
const messageEvent: Types.MessageEventType = 'auto_replied';
const messageEvent: Types.MessageEventType = 'scheduled';
const webhookEvent: Types.WebhookEvent = 'message.auto_replied';
const builtInRole: Types.BuiltInTeamRole = 'admin';
const suppression: Types.StoreSuppressionData = {
Expand Down Expand Up @@ -279,6 +301,7 @@ describe('generated api types', () => {
created_at: '2026-08-12T00:00:00Z',
};
const spamScore: Types.MessageListData['spam_score'] = 2.5;
const scheduledAt: Types.SendMailRequest['scheduled_at'] = '2026-08-27T09:00:00Z';

expect({
messageEvent,
Expand All @@ -295,6 +318,7 @@ describe('generated api types', () => {
domain,
sourceMessage,
spamScore,
scheduledAt,
}).toBeDefined();
});
});
13 changes: 13 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,19 @@ export class LettermintClient {
return response.json();
}

/** Make a PATCH request to the API. */
public async patch<T>(path: string, data?: unknown, config?: RequestConfig): Promise<T> {
const url = this.buildUrl(path, config?.params);
const headers = this.buildHeaders(config?.headers);
const response = await this.fetchWithTimeout(url, {
method: 'PATCH',
headers,
body: data ? JSON.stringify(data) : undefined,
});

return response.json();
}

/**
* Make a DELETE request to the API
*
Expand Down
11 changes: 11 additions & 0 deletions src/endpoints/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ export class MessagesEndpoint extends Endpoint {
return this.httpClient.get(`/messages/${this.pathSegment(messageId)}`);
}

public reschedule(
messageId: string,
payload: Types.RescheduleMessageRequest
): Promise<Types.RescheduleMessageResponse> {
return this.httpClient.patch(`/messages/${this.pathSegment(messageId)}`, payload);
}

public cancel(messageId: string): Promise<Types.RescheduleMessageResponse> {
return this.httpClient.post(`/messages/${this.pathSegment(messageId)}/cancel`);
}

public events(messageId: string): Promise<Types.MessageEventsResponse> {
return this.httpClient.get(`/messages/${this.pathSegment(messageId)}/events`);
}
Expand Down
6 changes: 6 additions & 0 deletions src/endpoints/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export class EmailEndpoint extends Endpoint {
return this;
}

/** Set the requested delivery time for the email. */
public scheduledAt(scheduledAt: string): this {
this.payload.scheduled_at = scheduledAt;
return this;
}

/**
* Set the HTML body of the email
*
Expand Down
18 changes: 16 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// biome-ignore-all lint: generated OpenAPI types use schema-shaped names.
// This file is generated by sdk-generator/node/generate-types.mjs.

export type MessageStatus = "pending" | "queued" | "suppressed" | "processed" | "delivered" | "opened" | "clicked" | "soft_bounced" | "hard_bounced" | "spam_complaint" | "failed" | "blocked" | "policy_rejected" | "unsubscribed";
export type MessageStatus = "scheduled" | "pending" | "queued" | "suppressed" | "processed" | "delivered" | "opened" | "clicked" | "soft_bounced" | "hard_bounced" | "spam_complaint" | "failed" | "blocked" | "policy_rejected" | "unsubscribed" | "canceled";

export interface SendMailRequest {
"route"?: string;
Expand All @@ -12,6 +12,7 @@ export interface SendMailRequest {
"bcc"?: string[];
"reply_to"?: string[];
"subject": string;
"scheduled_at"?: string;
"headers"?: Record<string, string>;
"metadata"?: Record<string, string>;
"tag"?: string | null;
Expand Down Expand Up @@ -42,6 +43,7 @@ export type SendBatchMailRequest = {
"bcc"?: string[];
"reply_to"?: string[];
"subject": string;
"scheduled_at"?: string;
"headers"?: Record<string, string>;
"metadata"?: Record<string, string>;
"tag"?: string | null;
Expand Down Expand Up @@ -141,6 +143,7 @@ export interface MessageData {
"type": MessageType;
"status": MessageStatus;
"status_changed_at": string | null;
"scheduled_at": string | null;
"tag": string | null;
"tags": {
"name": string;
Expand Down Expand Up @@ -173,12 +176,13 @@ export interface MessageEventData {
"timestamp": string;
}

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 type MessageEventType = "scheduled" | "rescheduled" | "canceled" | "released" | "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;
"type": MessageType;
"status": MessageStatus;
"scheduled_at": string | null;
"spam_score"?: number | null;
"from_email": string;
"from_name": string | null;
Expand Down Expand Up @@ -622,11 +626,21 @@ export type SendBatchEmailResponse = SendBatchMailResponse;
export type SendMailResponse = {
"message_id": string;
"status": MessageStatus;
"scheduled_at"?: string;
};
export type SendBatchMailResponse = {
"message_id": string;
"status": MessageStatus;
"scheduled_at"?: string;
}[];
export interface RescheduleMessageRequest {
"scheduled_at": string;
}
export type RescheduleMessageResponse = {
"message_id": string;
"status": MessageStatus | null;
"scheduled_at": string | null;
};
export type PingResponse = 200;
export type DomainIndexResponse = {
"data": DomainListData[];
Expand Down
Loading