diff --git a/src/analytics-forwarding.spec.ts b/src/analytics-forwarding.spec.ts new file mode 100644 index 0000000..86faaeb --- /dev/null +++ b/src/analytics-forwarding.spec.ts @@ -0,0 +1,79 @@ +import { Lettermint } from './lettermint'; +import type * as Types from './types'; + +it('uses the new analytics and report forwarding contracts', async () => { + const payload: Types.AnalyticsRequest = { + metrics: ['accepted', 'delivered'], + include: ['summary', 'time_series'], + interval: 'day', + filters: [{ dimension: 'project', operator: 'eq', values: ['project/id'] }], + sort: { metric: 'accepted', direction: 'desc' }, + limit: 10, + }; + const resource = { destination: null, verified: false, verified_at: null }; + const mockFetch = jest + .fn() + .mockResolvedValue({ ok: true, status: 200, json: async () => ({ data: resource }) }); + global.fetch = mockFetch; + const api = Lettermint.api('team-token'); + const analyticsResponse: Types.AnalyticsResponse = { + data: { summary: { metrics: { accepted: 12, delivery_rate: null } } }, + meta: { timezone: 'UTC' }, + pagination: { total_groups: 0, returned_groups: 0, next_cursor: null, truncated: false }, + }; + mockFetch.mockResolvedValueOnce({ ok: true, status: 200, json: async () => analyticsResponse }); + const analytics = await api.analytics(payload); + expect(analytics.meta.timezone).toBe('UTC'); + expect(analytics.data.summary?.metrics?.delivery_rate).toBeNull(); + expect((await api.projects.retrieveReportForwarding('project/id')).data).toEqual(resource); + await api.projects.updateReportForwarding('project/id', { destination: 'reports@example.com' }); + await api.projects.verifyReportForwarding('project/id', { code: '123456' }); + await api.projects.resendReportForwardingCode('project/id'); + const decode = jest.fn(() => { + throw new Error('204 must not be decoded'); + }); + mockFetch.mockResolvedValueOnce({ ok: true, status: 204, json: decode }); + await expect(api.projects.deleteReportForwarding('project/id')).resolves.toBeUndefined(); + expect(decode).not.toHaveBeenCalled(); + const calls = mockFetch.mock.calls; + expect( + calls.map(([url, options]) => [url.replace('https://api.lettermint.co/v1', ''), options.method]) + ).toEqual([ + ['/analytics', 'POST'], + ['/projects/project%2Fid/report-forwarding', 'GET'], + ['/projects/project%2Fid/report-forwarding', 'PUT'], + ['/projects/project%2Fid/report-forwarding/verify', 'POST'], + ['/projects/project%2Fid/report-forwarding/resend-code', 'POST'], + ['/projects/project%2Fid/report-forwarding', 'DELETE'], + ]); + expect(JSON.parse(calls[0][1].body)).toEqual(payload); + expect(JSON.parse(calls[2][1].body)).toEqual({ destination: 'reports@example.com' }); + expect(JSON.parse(calls[3][1].body)).toEqual({ code: '123456' }); + for (const [, options] of calls) { + expect(options.headers.Authorization).toBe('Bearer team-token'); + expect(options.headers).not.toHaveProperty('x-lettermint-token'); + } +}); + +it('keeps both suppression deletion results and project creation tokens', () => { + const deleted: Types.SuppressionDestroyResponse = { + success: true, + message: 'Deleted', + status: 'removed', + confidence: 0.9, + }; + const review: Types.SuppressionDestroyResponse = { + success: true, + message: 'Review', + status: 'review_ticket_created', + ticket_identifier: 'ticket-1', + }; + const created: Types.ProjectStoreResponse = { + data: {} as Types.ProjectData, + message: 'Created', + api_token: 'project-token', + }; + expect(deleted.message).toBe('Deleted'); + expect(review.ticket_identifier).toBe('ticket-1'); + expect(created.api_token).toBe('project-token'); +}); diff --git a/src/api-surface.spec.ts b/src/api-surface.spec.ts index 439e292..3f1a616 100644 --- a/src/api-surface.spec.ts +++ b/src/api-surface.spec.ts @@ -169,6 +169,12 @@ describe('public SDK surface', () => { describe('api endpoint coverage', () => { const documentedMethods = { + 'v1.analytics': 'analytics', + getReportForwarding: 'projects.retrieveReportForwarding', + updateReportForwarding: 'projects.updateReportForwarding', + deleteReportForwarding: 'projects.deleteReportForwarding', + verifyReportForwarding: 'projects.verifyReportForwarding', + resendReportForwardingCode: 'projects.resendReportForwardingCode', 'domain.index': 'domains.list', 'domain.store': 'domains.create', 'domain.show': 'domains.retrieve', @@ -224,7 +230,7 @@ describe('api endpoint coverage', () => { it('exposes documented API operations', () => { const api = Lettermint.api('api-token') as unknown as Record; - expect(Object.keys(documentedMethods)).toHaveLength(50); + expect(Object.keys(documentedMethods)).toHaveLength(56); for (const exposedPath of Object.values(documentedMethods)) { const segments = exposedPath.split('.'); @@ -293,7 +299,7 @@ describe('generated api types', () => { }; const sendResponse: Types.SendEmailResponse = { message_id: 'message_123', - status: 'delivered', + status: 'pending', sandbox: true, sandbox_result: 'clicked', }; diff --git a/src/client.ts b/src/client.ts index 2cce1f3..6dc2977 100644 --- a/src/client.ts +++ b/src/client.ts @@ -276,6 +276,7 @@ export class LettermintClient { headers, }); + if (response.status === 204) return undefined as T; return response.json(); } } diff --git a/src/endpoints/api.ts b/src/endpoints/api.ts index f86113a..f614033 100644 --- a/src/endpoints/api.ts +++ b/src/endpoints/api.ts @@ -83,6 +83,43 @@ export class MessagesEndpoint extends Endpoint { } export class ProjectsEndpoint extends Endpoint { + public async retrieveReportForwarding( + projectId: string + ): Promise { + return this.httpClient.get(`/projects/${this.pathSegment(projectId)}/report-forwarding`); + } + + public async updateReportForwarding( + projectId: string, + payload: Types.UpdateReportForwardingRequest + ): Promise { + return this.httpClient.put( + `/projects/${this.pathSegment(projectId)}/report-forwarding`, + payload + ); + } + + public async deleteReportForwarding(projectId: string): Promise { + await this.httpClient.delete(`/projects/${this.pathSegment(projectId)}/report-forwarding`); + } + + public async verifyReportForwarding( + projectId: string, + payload: Types.VerifyReportForwardingRequest + ): Promise { + return this.httpClient.post( + `/projects/${this.pathSegment(projectId)}/report-forwarding/verify`, + payload + ); + } + + public async resendReportForwardingCode( + projectId: string + ): Promise { + return this.httpClient.post( + `/projects/${this.pathSegment(projectId)}/report-forwarding/resend-code` + ); + } public list(params?: QueryParams): Promise { return this.httpClient.get('/projects', { params }); } diff --git a/src/lettermint.ts b/src/lettermint.ts index 547d692..005bf88 100644 --- a/src/lettermint.ts +++ b/src/lettermint.ts @@ -85,6 +85,10 @@ export class ApiClient { public async blockedFileTypes(): Promise { return this.client.get('/blocked-file-types'); } + + public async analytics(payload: Types.AnalyticsRequest): Promise { + return this.client.post('/analytics', payload); + } } export default Lettermint; diff --git a/src/types.ts b/src/types.ts index 42903da..d3c1d48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,7 +27,7 @@ export interface SendMailRequest { "track_opens"?: boolean; "track_clicks"?: boolean; "tls"?: TlsPolicy; -} | null; +}; "html"?: string | null; "text"?: string | null; "attachments"?: { @@ -56,7 +56,7 @@ export type SendBatchMailRequest = { "track_opens"?: boolean; "track_clicks"?: boolean; "tls"?: TlsPolicy; -} | null; +}; "html"?: string | null; "text"?: string | null; "attachments"?: { @@ -240,6 +240,7 @@ export interface ProjectData { export interface ProjectListData { "id": string; "name": string; + "delivery_mode": DeliveryMode; "smtp_enabled": boolean; "routes_count": number; "domains_count": number; @@ -250,7 +251,7 @@ export interface ProjectListData { export type RbacConflictCode = "stale_resource" | "owner_protected" | "last_owner" | "built_in_role_immutable" | "custom_role_requires_pro"; -export type RbacPermission = "team:manage" | "billing:manage" | "security:manage" | "audit:read" | "support:manage" | "members:read" | "members:manage" | "roles:manage" | "team_tokens:read" | "team_tokens:manage" | "team_tokens:rotate" | "team_tokens:revoke" | "projects:create" | "team_suppressions:read" | "team_suppressions:add" | "team_suppressions:remove" | "projects:read" | "projects:manage" | "projects:delete" | "routes:read" | "routes:manage" | "routes:delete" | "domains:read" | "domains:manage" | "domains:delete" | "project_tokens:read" | "project_tokens:manage" | "project_tokens:rotate" | "project_tokens:revoke" | "webhooks:read" | "webhooks:manage" | "webhooks:delete" | "webhooks:rotate_secret" | "stats:read" | "messages:read" | "messages:read_content" | "messages:send" | "suppressions:read" | "suppressions:add" | "suppressions:remove"; +export type RbacPermission = "team:manage" | "billing:manage" | "security:manage" | "audit:read" | "support:manage" | "members:read" | "members:manage" | "roles:manage" | "team_tokens:read" | "team_tokens:manage" | "team_tokens:rotate" | "team_tokens:revoke" | "projects:create" | "team_suppressions:read" | "team_suppressions:add" | "team_suppressions:remove" | "projects:read" | "projects:manage" | "projects:delete" | "routes:read" | "routes:manage" | "routes:delete" | "domains:read" | "domains:manage" | "domains:delete" | "project_tokens:read" | "project_tokens:manage" | "project_tokens:rotate" | "project_tokens:revoke" | "webhooks:read" | "webhooks:manage" | "webhooks:delete" | "webhooks:rotate_secret" | "stats:read" | "analytics:read" | "messages:read" | "messages:read_content" | "messages:send" | "suppressions:read" | "suppressions:add" | "suppressions:remove"; export type RecordType = "TXT" | "CNAME" | "MX"; @@ -397,15 +398,21 @@ export interface StoreDomainData { export interface StoreProjectData { "name": string; "smtp_enabled"?: boolean; + "delivery_mode"?: DeliveryMode; "initial_routes"?: InitialRoutes; "short_token"?: boolean; - "delivery_mode"?: DeliveryMode; + "redact_email_content"?: boolean; } export interface StoreRouteData { "name": string; "route_type": RouteType; "slug"?: string | null; + "settings"?: UpdateRouteSettingsData | null; + "inbound_settings"?: UpdateRouteInboundSettingsData | null; + "inbound_domain"?: string | null; + "inbound_spam_threshold"?: number | null; + "attachment_delivery"?: AttachmentDelivery | null; } export interface StoreSuppressionData { @@ -538,6 +545,9 @@ export interface UpdateRouteData { "name"?: string | null; "settings"?: UpdateRouteSettingsData | null; "inbound_settings"?: UpdateRouteInboundSettingsData | null; + "inbound_domain"?: string | null; + "inbound_spam_threshold"?: number | null; + "attachment_delivery"?: AttachmentDelivery | null; } export interface UpdateRouteInboundSettingsData { @@ -558,7 +568,7 @@ export interface UpdateRouteSettingsData { } export interface UpdateTeamData { - "name"?: string | null; + "name"?: string; } export interface UpdateTeamMemberAssignmentData { @@ -627,6 +637,7 @@ export interface WebhookDeliveryListData { "source_project_id": string | null; "source_route_id": string | null; "status": WebhookDeliveryStatus; + "sandbox": boolean; "attempt_number": number; "http_status_code": number | null; "duration_ms": number | null; @@ -641,13 +652,14 @@ export type WebhookEvent = "message.created" | "message.sent" | "message.deliver export interface WebhookListData { "id": string; "scope": WebhookScope; - "project_ids": string[]; - "route_ids": string[]; + "project_ids": (string)[]; + "route_ids": (string)[]; "route_id": string | null; "name": string; "url": string; - "events": string[]; + "events": (string)[]; "enabled": boolean; + "delivery_mode_filter": WebhookDeliveryModeFilter; "last_called_at": string | null; "created_at": string; "updated_at": string; @@ -680,18 +692,14 @@ export type WebhookDeliveryModeFilter = "live" | "sandbox" | "both"; export type EmailPayload = SendMailRequest; export type SendEmailResponse = SendMailResponse; export type SendBatchEmailResponse = SendBatchMailResponse; -export type SendMailResponse = { - "message_id": string | null; - "status": MessageStatus; - "scheduled_at": string; - "sandbox"?: true; - "sandbox_result"?: SandboxResult; -} | { - "message_id": string | null; - "status": MessageStatus; +export interface SendMailResponse { + "message_id": string; + "status": "pending" | "scheduled"; "sandbox"?: true; "sandbox_result"?: SandboxResult; -}; + "scheduled_at"?: string; +} + export type SendBatchMailResponse = { "message_id": string; "status": MessageStatus; @@ -699,7 +707,7 @@ export type SendBatchMailResponse = { "sandbox"?: true; "sandbox_result"?: SandboxResult; }[]; -export type PingResponse = 200; +export type PingResponse = string; export type DomainIndexResponse = { "data": DomainListData[]; "path": string | null; @@ -787,11 +795,12 @@ export type ProjectIndexResponse = { "prev_page_url": string | null; }; export type ProjectStoreRequest = StoreProjectData; -export type ProjectStoreResponse = { +export interface ProjectStoreResponse { "data": ProjectData; - "message": "Project created successfully."; - "api_token": string; -}; + "message": string; + "api_token"?: string; +} + export type ProjectShowResponse = ProjectData; export type ProjectUpdateRequest = UpdateProjectData; export type ProjectUpdateResponse = { @@ -829,22 +838,24 @@ export type RouteUpdateResponse = { export type RouteDestroyResponse = { "message": "Route deleted successfully."; }; -export type RouteVerifyInboundDomainResponse = { +export interface RouteVerifyInboundDomainResponse { "data": { - "verified": boolean; + "verified": true; "message": "Inbound domain verified successfully."; }; -}; +} + export type StatsIndexResponse = StatsData; -export type SuppressionIndexResponse = { - "data": SuppressedRecipientData[]; - "path": string | null; +export interface SuppressionIndexResponse { + "data": (SuppressedRecipientData)[]; + "path": string; "per_page": number; "next_cursor": string | null; "next_page_url": string | null; "prev_cursor": string | null; "prev_page_url": string | null; -}; +} + export type SuppressionStoreRequest = StoreSuppressionData; export type SuppressionStoreResponse = { "message": string | "No emails were added."; @@ -853,12 +864,14 @@ export type SuppressionStoreResponse = { "skipped": string[]; }; }; -export type SuppressionDestroyResponse = { - "success": boolean; - "status": "removed"; +export interface SuppressionDestroyResponse { + "success": true; + "status": "removed" | "review_ticket_created" | "review_ticket_exists"; "message": string; "confidence"?: number; -}; + "ticket_identifier"?: string; +} + export type TeamShowResponse = TeamData; export type TeamUpdateRequest = UpdateTeamData; export type TeamUpdateResponse = { @@ -922,3 +935,940 @@ export type WebhookDeliveriesResponse = { "prev_page_url": string | null; }; export type WebhookShowDeliveryResponse = WebhookDeliveryData; + +export interface AnalyticsResponseData { + "data": Record; + "meta": Record; + "pagination": (string)[]; +} + +export interface ProjectCreatedData { + "data": ProjectData; + "message": string; + "api_token"?: string; +} + +export interface ReportForwardingRequest { + "destination": string; +} + +export interface ReportForwardingResource { + "destination": string | null; + "verified": boolean; + "verified_at": string | null; +} + +export interface VerifyReportForwardingRequest { + "code": string; +} + +export interface AnalyticsRequest { + "metrics": ("accepted" | "processed" | "suppressed" | "policy_rejected" | "application_failed" | "mta_accepted" | "canceled" | "messages" | "delivered" | "bounced" | "soft_bounced" | "administratively_bounced" | "deferred_recipients" | "deferred_events" | "delivery_attempts" | "attempted_recipients" | "transport_outcome_recipients" | "effective_delivered" | "open_tracked_delivered" | "click_tracked_delivered" | "out_of_band_bounced_recipients" | "out_of_band_bounce_events" | "complained" | "unsubscribed" | "human_opens" | "human_opens_events" | "human_clicks" | "human_clicks_events" | "machine_opens" | "machine_opens_events" | "machine_clicks" | "machine_clicks_events" | "privacy_opens" | "privacy_opens_events" | "privacy_clicks" | "privacy_clicks_events" | "bot_opens" | "bot_opens_events" | "bot_clicks" | "bot_clicks_events" | "scanner_opens" | "scanner_opens_events" | "scanner_clicks" | "scanner_clicks_events" | "observed_opens" | "observed_opens_events" | "observed_clicks" | "observed_clicks_events" | "delivery_rate" | "effective_delivery_rate" | "bounce_rate" | "deferral_rate" | "complaint_rate" | "human_open_rate" | "human_click_rate" | "processing_latency_p50_ms" | "processing_latency_p95_ms" | "processing_latency_p99_ms" | "processing_latency_samples" | "delivery_latency_p50_ms" | "delivery_latency_p95_ms" | "delivery_latency_p99_ms" | "delivery_latency_samples" | "total_latency_p50_ms" | "total_latency_p95_ms" | "total_latency_p99_ms" | "total_latency_samples")[]; + "from"?: string; + "to"?: string; + "timezone"?: string; + "include"?: ("summary" | "time_series" | "breakdown")[]; + "group_by"?: (string)[]; + "filters"?: ({ + "dimension": string; + "operator": "eq" | "in" | "not_in" | "is_null" | "is_not_null"; + "values"?: (string)[]; +})[]; + "interval"?: "hour" | "day"; + "compare"?: "previous_period"; + "include_trend"?: boolean; + "sort"?: { + "metric": "accepted" | "processed" | "suppressed" | "policy_rejected" | "application_failed" | "mta_accepted" | "canceled" | "messages" | "delivered" | "bounced" | "soft_bounced" | "administratively_bounced" | "deferred_recipients" | "deferred_events" | "delivery_attempts" | "attempted_recipients" | "transport_outcome_recipients" | "effective_delivered" | "open_tracked_delivered" | "click_tracked_delivered" | "out_of_band_bounced_recipients" | "out_of_band_bounce_events" | "complained" | "unsubscribed" | "human_opens" | "human_opens_events" | "human_clicks" | "human_clicks_events" | "machine_opens" | "machine_opens_events" | "machine_clicks" | "machine_clicks_events" | "privacy_opens" | "privacy_opens_events" | "privacy_clicks" | "privacy_clicks_events" | "bot_opens" | "bot_opens_events" | "bot_clicks" | "bot_clicks_events" | "scanner_opens" | "scanner_opens_events" | "scanner_clicks" | "scanner_clicks_events" | "observed_opens" | "observed_opens_events" | "observed_clicks" | "observed_clicks_events" | "delivery_rate" | "effective_delivery_rate" | "bounce_rate" | "deferral_rate" | "complaint_rate" | "human_open_rate" | "human_click_rate" | "processing_latency_p50_ms" | "processing_latency_p95_ms" | "processing_latency_p99_ms" | "processing_latency_samples" | "delivery_latency_p50_ms" | "delivery_latency_p95_ms" | "delivery_latency_p99_ms" | "delivery_latency_samples" | "total_latency_p50_ms" | "total_latency_p95_ms" | "total_latency_p99_ms" | "total_latency_samples"; + "direction": "asc" | "desc"; +}; + "limit"?: number; + "cursor"?: string; +} + +export type UpdateReportForwardingRequest = ReportForwardingRequest; + +export interface AnalyticsResponse { + "data": { + "summary"?: { + "metrics"?: { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases"?: { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; + "previous"?: { + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; +}; + "change"?: Record; +}; + "time_series"?: ({ + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; + "previous"?: { + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; +}; + "change"?: Record; + "from": string; + "to": string; + "available": boolean; + "partial": boolean; +})[]; + "breakdown"?: ({ + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; + "previous"?: { + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; +}; + "change"?: Record; + "dimensions": Record; + "trend"?: ({ + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; + "previous"?: { + "metrics": { + "accepted"?: number | null; + "processed"?: number | null; + "suppressed"?: number | null; + "policy_rejected"?: number | null; + "application_failed"?: number | null; + "mta_accepted"?: number | null; + "canceled"?: number | null; + "messages"?: number | null; + "delivered"?: number | null; + "bounced"?: number | null; + "soft_bounced"?: number | null; + "administratively_bounced"?: number | null; + "deferred_recipients"?: number | null; + "deferred_events"?: number | null; + "delivery_attempts"?: number | null; + "attempted_recipients"?: number | null; + "transport_outcome_recipients"?: number | null; + "effective_delivered"?: number | null; + "open_tracked_delivered"?: number | null; + "click_tracked_delivered"?: number | null; + "out_of_band_bounced_recipients"?: number | null; + "out_of_band_bounce_events"?: number | null; + "complained"?: number | null; + "unsubscribed"?: number | null; + "human_opens"?: number | null; + "human_opens_events"?: number | null; + "human_clicks"?: number | null; + "human_clicks_events"?: number | null; + "machine_opens"?: number | null; + "machine_opens_events"?: number | null; + "machine_clicks"?: number | null; + "machine_clicks_events"?: number | null; + "privacy_opens"?: number | null; + "privacy_opens_events"?: number | null; + "privacy_clicks"?: number | null; + "privacy_clicks_events"?: number | null; + "bot_opens"?: number | null; + "bot_opens_events"?: number | null; + "bot_clicks"?: number | null; + "bot_clicks_events"?: number | null; + "scanner_opens"?: number | null; + "scanner_opens_events"?: number | null; + "scanner_clicks"?: number | null; + "scanner_clicks_events"?: number | null; + "observed_opens"?: number | null; + "observed_opens_events"?: number | null; + "observed_clicks"?: number | null; + "observed_clicks_events"?: number | null; + "delivery_rate"?: number | null; + "effective_delivery_rate"?: number | null; + "bounce_rate"?: number | null; + "deferral_rate"?: number | null; + "complaint_rate"?: number | null; + "human_open_rate"?: number | null; + "human_click_rate"?: number | null; + "processing_latency_p50_ms"?: number | null; + "processing_latency_p95_ms"?: number | null; + "processing_latency_p99_ms"?: number | null; + "processing_latency_samples"?: number | null; + "delivery_latency_p50_ms"?: number | null; + "delivery_latency_p95_ms"?: number | null; + "delivery_latency_p99_ms"?: number | null; + "delivery_latency_samples"?: number | null; + "total_latency_p50_ms"?: number | null; + "total_latency_p95_ms"?: number | null; + "total_latency_p99_ms"?: number | null; + "total_latency_samples"?: number | null; +}; + "rate_bases": { + "delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "effective_delivery_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "bounce_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "deferral_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "complaint_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_open_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; + "human_click_rate"?: { + "numerator": number | null; + "denominator": number | null; +}; +}; +}; + "change"?: Record; + "from": string; + "to": string; + "available": boolean; + "partial": boolean; +})[]; +})[]; +}; + "meta": { + "time_basis"?: "event"; + "timezone"?: string; + "interval"?: "hour" | "day"; + "from"?: string; + "to"?: string; + "effective_to"?: string; + "alignment"?: "hour" | "day"; + "generated_at"?: string; + "available_since"?: string; + "partial"?: boolean; + "ongoing"?: boolean; + "collection_completeness"?: "best_effort"; + "last_ingested_at"?: string | null; + "metric_definition_version"?: string; + "ranked_group_limit"?: number; + "comparison"?: { + "from": string; + "to": string; + "partial": boolean; +}; +}; + "pagination": { + "total_groups": number; + "returned_groups": number; + "next_cursor": string | null; + "truncated": boolean; +}; +} + +export interface GetReportForwardingResponse { + "data": ReportForwardingResource; +} + +export interface UpdateReportForwardingResponse { + "data": ReportForwardingResource; +} + +export interface VerifyReportForwardingResponse { + "data": ReportForwardingResource; +} + +export interface ResendReportForwardingCodeResponse { + "data": ReportForwardingResource; +}