@develemail/sdk 0.1.1 → 0.4.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface DevelEmailConfig {\n apiKey: string;\n baseUrl: string;\n}\n\nexport interface SendEmailInput {\n from: string;\n to: string | string[];\n subject: string;\n html?: string;\n text?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmailResponse {\n id: string;\n status: string;\n}\n\nexport class DevelEmailError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = 'DevelEmailError';\n }\n}\n\nclass EmailsNamespace {\n constructor(private readonly config: DevelEmailConfig) {}\n\n async send(input: SendEmailInput): Promise<EmailResponse> {\n return this.request<EmailResponse>('POST', '/v1/emails', input);\n }\n\n async get(id: string): Promise<EmailResponse> {\n return this.request<EmailResponse>('GET', `/v1/emails/${id}`);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const res = await fetch(`${this.config.baseUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!res.ok) {\n const data = await res.json().catch(() => ({}));\n throw new DevelEmailError(\n res.status,\n (data as { message?: string }).message ?? res.statusText,\n );\n }\n\n return res.json() as Promise<T>;\n }\n}\n\nexport class DevelEmail {\n readonly emails: EmailsNamespace;\n\n constructor(config: DevelEmailConfig) {\n this.emails = new EmailsNamespace(config);\n }\n}\n\nexport interface WebhookEvent {\n event: string;\n emailId: string | null;\n status: string | null;\n recipient: string | null;\n timestamp: string;\n metadata: Record<string, unknown>;\n}\n\nexport async function verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n): Promise<boolean> {\n const expected = signature.startsWith('sha256=') ? signature.slice(7) : signature;\n\n const enc = new TextEncoder();\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n enc.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n\n const mac = await globalThis.crypto.subtle.sign('HMAC', key, enc.encode(payload));\n const computed = Array.from(new Uint8Array(mac))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n\n if (computed.length !== expected.length) return false;\n let diff = 0;\n for (let i = 0; i < computed.length; i++) {\n diff |= computed.charCodeAt(i) ^ expected.charCodeAt(i);\n }\n return diff === 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACkB,QAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;AAEA,IAAM,kBAAN,MAAsB;AAAA,EACpB,YAA6B,QAA0B;AAA1B;AAAA,EAA2B;AAAA,EAA3B;AAAA,EAE7B,MAAM,KAAK,OAA+C;AACxD,WAAO,KAAK,QAAuB,QAAQ,cAAc,KAAK;AAAA,EAChE;AAAA,EAEA,MAAM,IAAI,IAAoC;AAC5C,WAAO,KAAK,QAAuB,OAAO,cAAc,EAAE,EAAE;AAAA,EAC9D;AAAA,EAEA,MAAc,QACZ,QACA,MACA,MACY;AACZ,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,GAAG,IAAI,IAAI;AAAA,MACvD;AAAA,MACA,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,OAAO,MAAM;AAAA,QAC3C,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,IACpD,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,YAAM,IAAI;AAAA,QACR,IAAI;AAAA,QACH,KAA8B,WAAW,IAAI;AAAA,MAChD;AAAA,IACF;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;AAEO,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EAET,YAAY,QAA0B;AACpC,SAAK,SAAS,IAAI,gBAAgB,MAAM;AAAA,EAC1C;AACF;AAWA,eAAsB,uBACpB,SACA,WACA,QACkB;AAClB,QAAM,WAAW,UAAU,WAAW,SAAS,IAAI,UAAU,MAAM,CAAC,IAAI;AAExE,QAAM,MAAM,IAAI,YAAY;AAC5B,QAAM,MAAM,MAAM,WAAW,OAAO,OAAO;AAAA,IACzC;AAAA,IACA,IAAI,OAAO,MAAM;AAAA,IACjB,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAChC;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,MAAM,MAAM,WAAW,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,OAAO,OAAO,CAAC;AAChF,QAAM,WAAW,MAAM,KAAK,IAAI,WAAW,GAAG,CAAC,EAC5C,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AAEV,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAQ,SAAS,WAAW,CAAC,IAAI,SAAS,WAAW,CAAC;AAAA,EACxD;AACA,SAAO,SAAS;AAClB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts","../../core/src/reserved-domains.ts","../src/attachment-encoding.ts","../src/emails.ts","../src/domains.ts","../src/templates.ts","../src/campaigns.ts","../src/contacts.ts","../src/lists.ts","../src/sms.ts","../src/webhook.ts","../src/types/emails.ts","../src/types/sms.ts"],"sourcesContent":["import { HttpClient } from './client.js';\nimport { Emails } from './emails.js';\nimport { Domains } from './domains.js';\nimport { Templates } from './templates.js';\nimport { Campaigns } from './campaigns.js';\nimport { Contacts } from './contacts.js';\nimport { Lists } from './lists.js';\nimport { Sms } from './sms.js';\nimport type { DevelEmailConfig } from './types.js';\n\nexport class DevelEmail {\n readonly emails: Emails;\n readonly domains: Domains;\n readonly templates: Templates;\n readonly campaigns: Campaigns;\n readonly contacts: Contacts;\n readonly lists: Lists;\n readonly sms: Sms;\n\n constructor(config: DevelEmailConfig) {\n const http = new HttpClient(config);\n this.emails = new Emails(http);\n this.domains = new Domains(http);\n this.templates = new Templates(http);\n this.campaigns = new Campaigns(http);\n this.contacts = new Contacts(http);\n this.lists = new Lists(http);\n this.sms = new Sms(http);\n }\n}\n\nexport { DevelEmailError } from './errors.js';\nexport { verifyWebhookSignature } from './webhook.js';\nexport { EMAIL_STATUSES, EMAIL_EVENT_TYPES, SMS_STATUSES } from './types.js';\nexport type {\n DevelEmailConfig,\n SendEmailInput,\n AttachmentInput,\n WireAttachmentInput,\n SendOptions,\n EmailResponse,\n EmailStatus,\n EmailEventType,\n DeliveryEventData,\n BatchEmailResultItem,\n BatchEmailResponse,\n Domain,\n DomainCreateInput,\n Template,\n TemplateCreateInput,\n TemplateUpdateInput,\n WebhookEvent,\n CampaignStatus,\n Campaign,\n CampaignListQuery,\n CampaignCreateInput,\n CampaignUpdateInput,\n CampaignStats,\n CampaignProviderStats,\n Contact,\n ContactListQuery,\n ContactListResponse,\n ContactCreateInput,\n ContactUpdateInput,\n ContactBulkContactInput,\n ContactBulkInput,\n ContactBulkResponse,\n ContactListItem,\n List,\n ListWithCount,\n ListCreateInput,\n ListUpdateInput,\n ListMembersQuery,\n ListMembersResponse,\n SmsStatus,\n SendSmsInput,\n BatchSmsResponse,\n BatchSmsResultItem,\n SmsMessageResponse,\n SmsListResponse,\n SmsListQuery,\n} from './types.js';\n","export class DevelEmailError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n public readonly code?: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'DevelEmailError';\n }\n}\n","import { DevelEmailError } from './errors.js';\nimport type { DevelEmailConfig } from './types.js';\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 500;\nconst MAX_DELAY_MS = 30_000;\nconst MAX_RETRY_AFTER_MS = 10_000;\nconst CONFLICT_RETRY_DELAY_MS = 1_000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\nfunction isIdempotencyConflict(status: number, code?: string): boolean {\n return status === 409 && code === 'idempotency_conflict';\n}\n\nfunction parseRetryAfterMs(header: string | null): number | null {\n if (!header) return null;\n const seconds = Number(header);\n if (!Number.isFinite(seconds) || seconds < 0) return null;\n return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);\n}\n\ninterface ParsedError {\n message: string;\n code?: string;\n details?: Record<string, unknown>;\n}\n\nfunction parseErrorBody(body: unknown): ParsedError {\n if (typeof body === 'object' && body !== null) {\n const b = body as Record<string, unknown>;\n const { message, error, statusCode, ...rest } = b;\n const code =\n typeof b.code === 'string'\n ? b.code\n : typeof error === 'string'\n ? error\n : undefined;\n return {\n message: typeof message === 'string' ? message : 'Unknown error',\n code,\n details: Object.keys(rest).length > 0 ? rest : undefined,\n };\n }\n return { message: 'Unknown error' };\n}\n\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly maxRetries: number;\n\n constructor(config: DevelEmailConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n }\n\n async get<T>(path: string): Promise<T> {\n return this.request<T>('GET', path);\n }\n\n async post<T>(\n path: string,\n body: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n return this.request<T>('POST', path, body, extraHeaders);\n }\n\n async put<T>(\n path: string,\n body: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n return this.request<T>('PUT', path, body, extraHeaders);\n }\n\n async patch<T>(\n path: string,\n body: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n return this.request<T>('PATCH', path, body, extraHeaders);\n }\n\n async delete<T>(\n path: string,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n return this.request<T>('DELETE', path, undefined, extraHeaders);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n };\n const init: RequestInit = {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n };\n\n let lastError: unknown;\n let retriedConflict = false;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n let res: Response;\n try {\n res = await fetch(url, init);\n } catch (err) {\n lastError = err;\n if (attempt < this.maxRetries) {\n await sleep(this.backoff(attempt));\n continue;\n }\n throw new DevelEmailError(\n 0,\n err instanceof Error ? err.message : 'Network error',\n 'network_error',\n );\n }\n\n if (res.ok) {\n if (res.status === 204) return undefined as T;\n return res.json() as Promise<T>;\n }\n\n const errorBody = await res.json().catch(() => ({}));\n const parsed = parseErrorBody(errorBody);\n\n const conflictRetry =\n isIdempotencyConflict(res.status, parsed.code) && !retriedConflict;\n if (\n (isRetryable(res.status) || conflictRetry) &&\n attempt < this.maxRetries\n ) {\n if (conflictRetry) retriedConflict = true;\n const retryAfterMs = parseRetryAfterMs(res.headers.get('Retry-After'));\n const delay =\n retryAfterMs ??\n (conflictRetry ? CONFLICT_RETRY_DELAY_MS : this.backoff(attempt));\n await sleep(delay);\n lastError = new DevelEmailError(\n res.status,\n parsed.message,\n parsed.code,\n parsed.details,\n );\n continue;\n }\n\n throw new DevelEmailError(\n res.status,\n parsed.message,\n parsed.code,\n parsed.details,\n );\n }\n\n if (lastError instanceof DevelEmailError) throw lastError;\n throw new DevelEmailError(\n 0,\n lastError instanceof Error ? lastError.message : 'Request failed',\n 'network_error',\n );\n }\n\n private backoff(attempt: number): number {\n const delay = BASE_DELAY_MS * 2 ** attempt;\n const jitter = delay * 0.2 * Math.random();\n return Math.min(delay + jitter, MAX_DELAY_MS);\n }\n}\n","export const RESERVED_DOMAINS = ['example.com', 'example.net', 'example.org'];\n\n// 'local' is reserved for mDNS by RFC 6762 — it can never resolve to a\n// public MX, so mail to it will only ever sit deferring in the queue.\nexport const RESERVED_TLDS = [\n 'test',\n 'example',\n 'invalid',\n 'localhost',\n 'local',\n];\n\nexport function isReservedDomain(domain: string): boolean {\n const normalized = domain.trim().toLowerCase().replace(/\\.$/, '');\n if (!normalized) return false;\n\n if (RESERVED_DOMAINS.includes(normalized)) return true;\n\n const tld = normalized.split('.').pop() ?? '';\n return RESERVED_TLDS.includes(tld);\n}\n\nexport const SANDBOX_DOMAIN = 'sandbox.develemail.com';\n\nexport function isSandboxRecipient(address: string): boolean {\n const domain = address.split('@')[1] ?? '';\n return domain.trim().toLowerCase().replace(/\\.$/, '') === SANDBOX_DOMAIN;\n}\n","import type { AttachmentInput, WireAttachmentInput } from './types.js';\n\n/**\n * A `string` is treated as already-base64 (matching the wire format) and\n * passed through untouched. A `Uint8Array` (covers Node `Buffer`) is\n * base64-encoded here before the request goes out. Pure and exported so the\n * encoding path is directly unit-testable without mocking `fetch`.\n */\nexport function encodeAttachmentContent(content: string | Uint8Array): string {\n return typeof content === 'string'\n ? content\n : Buffer.from(content).toString('base64');\n}\n\n/**\n * Converts a `send()`/`batch()` caller's ergonomic attachment list into the\n * wire shape `AttachmentInput.content` is base64-encoded via\n * `encodeAttachmentContent`, everything else passes through.\n */\nexport function encodeAttachments(\n attachments: AttachmentInput[] | undefined,\n): WireAttachmentInput[] | undefined {\n if (!attachments) return undefined;\n return attachments.map((attachment) => ({\n filename: attachment.filename,\n contentType: attachment.contentType,\n content: encodeAttachmentContent(attachment.content),\n }));\n}\n","import { isReservedDomain } from '@develemail/core';\nimport type { HttpClient } from './client.js';\nimport { DevelEmailError } from './errors.js';\nimport { encodeAttachments } from './attachment-encoding.js';\nimport type {\n SendEmailInput,\n SendOptions,\n EmailResponse,\n BatchEmailResponse,\n BatchEmailResultItem,\n} from './types.js';\n\n// Converts the ergonomic `attachments` field (content: string | Uint8Array)\n// to the wire shape (content: always base64 string) right before the\n// request is serialized — every other field passes through untouched.\nfunction toWirePayload(input: SendEmailInput): unknown {\n return { ...input, attachments: encodeAttachments(input.attachments) };\n}\n\nfunction extractDomain(email: string): string {\n return email.split('@')[1] ?? '';\n}\n\n// Mirrors apps/api/src/domains/emails/errors.ts's ReservedRecipientDomainError\n// message so a caller sees the same wording whether the API or the SDK caught it.\nfunction reservedRecipientError(recipient: string): DevelEmailError {\n const domain = extractDomain(recipient);\n return new DevelEmailError(\n 400,\n `Recipient '${recipient}' uses reserved domain '${domain}', which cannot receive mail`,\n 'reserved_recipient_domain',\n { recipient, domain },\n );\n}\n\nfunction findReservedRecipient(input: SendEmailInput): string | null {\n const toAddresses = Array.isArray(input.to) ? input.to : [input.to];\n return (\n toAddresses.find((addr) => isReservedDomain(extractDomain(addr))) ?? null\n );\n}\n\nexport class Emails {\n constructor(private readonly http: HttpClient) {}\n\n async send(\n input: SendEmailInput,\n options?: SendOptions,\n ): Promise<EmailResponse> {\n const reserved = findReservedRecipient(input);\n if (reserved) throw reservedRecipientError(reserved);\n\n const key = options?.idempotencyKey ?? crypto.randomUUID();\n return this.http.post<EmailResponse>('/v1/emails', toWirePayload(input), {\n 'Idempotency-Key': key,\n });\n }\n\n // Rejects reserved recipients client-side but, matching the server's\n // per-item batch semantics, does not fail the whole batch: deliverable\n // items still go out, and each reserved item is named in `results` with\n // an `error` instead of being silently dropped.\n async batch(\n emails: SendEmailInput[],\n options?: SendOptions,\n ): Promise<BatchEmailResponse> {\n const rejections = new Map<number, BatchEmailResultItem>();\n emails.forEach((email, index) => {\n const reserved = findReservedRecipient(email);\n if (reserved) {\n rejections.set(index, {\n index,\n error: reservedRecipientError(reserved).message,\n code: 'reserved_recipient_domain',\n });\n }\n });\n\n const deliverable = emails.filter((_, index) => !rejections.has(index));\n\n if (deliverable.length === 0) {\n return {\n results: emails.map((_, index) => rejections.get(index)!),\n created: 0,\n errors: rejections.size,\n };\n }\n\n const key = options?.idempotencyKey ?? crypto.randomUUID();\n const response = await this.http.post<BatchEmailResponse>(\n '/v1/emails/batch',\n { emails: deliverable.map(toWirePayload) },\n { 'Idempotency-Key': key },\n );\n\n const results: BatchEmailResultItem[] = [];\n let sent = 0;\n for (let index = 0; index < emails.length; index++) {\n const rejection = rejections.get(index);\n results.push(rejection ?? { ...response.results[sent++]!, index });\n }\n\n return {\n results,\n created: response.created,\n errors: response.errors + rejections.size,\n limitReached: response.limitReached,\n };\n }\n\n async get(id: string): Promise<EmailResponse> {\n return this.http.get<EmailResponse>(`/v1/emails/${encodeURIComponent(id)}`);\n }\n}\n","import type { HttpClient } from './client.js';\nimport type { Domain, DomainCreateInput } from './types.js';\n\nexport class Domains {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<Domain[]> {\n return this.http.get<Domain[]>('/domains');\n }\n\n async get(id: string): Promise<Domain> {\n return this.http.get<Domain>(`/domains/${encodeURIComponent(id)}`);\n }\n\n async create(input: DomainCreateInput): Promise<Domain> {\n return this.http.post<Domain>('/domains', input);\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete<void>(`/domains/${encodeURIComponent(id)}`);\n }\n\n async verify(id: string): Promise<Domain> {\n return this.http.post<Domain>(\n `/domains/${encodeURIComponent(id)}/verify`,\n undefined,\n );\n }\n}\n","import type { HttpClient } from './client.js';\nimport type {\n Template,\n TemplateCreateInput,\n TemplateUpdateInput,\n} from './types.js';\n\nexport class Templates {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<Template[]> {\n return this.http.get<Template[]>('/templates');\n }\n\n async get(id: string): Promise<Template> {\n return this.http.get<Template>(`/templates/${encodeURIComponent(id)}`);\n }\n\n async create(input: TemplateCreateInput): Promise<Template> {\n return this.http.post<Template>('/templates', input);\n }\n\n async update(id: string, input: TemplateUpdateInput): Promise<Template> {\n return this.http.put<Template>(\n `/templates/${encodeURIComponent(id)}`,\n input,\n );\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete<void>(`/templates/${encodeURIComponent(id)}`);\n }\n}\n","import type { HttpClient } from './client.js';\nimport type {\n Campaign,\n CampaignCreateInput,\n CampaignListQuery,\n CampaignProviderStats,\n CampaignStats,\n CampaignUpdateInput,\n} from './types.js';\n\nfunction toQueryString(query: CampaignListQuery): string {\n const params = new URLSearchParams();\n params.set('projectId', query.projectId);\n if (query.status) params.set('status', query.status);\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.offset !== undefined) params.set('offset', String(query.offset));\n return params.toString();\n}\n\nexport class Campaigns {\n constructor(private readonly http: HttpClient) {}\n\n async list(query: CampaignListQuery): Promise<Campaign[]> {\n return this.http.get<Campaign[]>(`/campaigns?${toQueryString(query)}`);\n }\n\n async get(id: string): Promise<Campaign> {\n return this.http.get<Campaign>(`/campaigns/${encodeURIComponent(id)}`);\n }\n\n async create(input: CampaignCreateInput): Promise<Campaign> {\n return this.http.post<Campaign>('/campaigns', input);\n }\n\n async update(id: string, input: CampaignUpdateInput): Promise<Campaign> {\n return this.http.patch<Campaign>(\n `/campaigns/${encodeURIComponent(id)}`,\n input,\n );\n }\n\n async send(id: string): Promise<Campaign> {\n return this.http.post<Campaign>(\n `/campaigns/${encodeURIComponent(id)}/send`,\n undefined,\n );\n }\n\n async pause(id: string): Promise<Campaign> {\n return this.http.post<Campaign>(\n `/campaigns/${encodeURIComponent(id)}/pause`,\n undefined,\n );\n }\n\n async resume(id: string): Promise<Campaign> {\n return this.http.post<Campaign>(\n `/campaigns/${encodeURIComponent(id)}/resume`,\n undefined,\n );\n }\n\n async cancel(id: string): Promise<Campaign & { note: string }> {\n return this.http.post<Campaign & { note: string }>(\n `/campaigns/${encodeURIComponent(id)}/cancel`,\n undefined,\n );\n }\n\n async duplicate(id: string): Promise<Campaign> {\n return this.http.post<Campaign>(\n `/campaigns/${encodeURIComponent(id)}/duplicate`,\n undefined,\n );\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete<void>(`/campaigns/${encodeURIComponent(id)}`);\n }\n\n async stats(id: string): Promise<CampaignStats> {\n return this.http.get<CampaignStats>(\n `/campaigns/${encodeURIComponent(id)}/stats`,\n );\n }\n\n async providerStats(id: string): Promise<CampaignProviderStats[]> {\n return this.http.get<CampaignProviderStats[]>(\n `/campaigns/${encodeURIComponent(id)}/provider-stats`,\n );\n }\n}\n","import type { HttpClient } from './client.js';\nimport type {\n Contact,\n ContactBulkInput,\n ContactBulkResponse,\n ContactCreateInput,\n ContactListItem,\n ContactListQuery,\n ContactListResponse,\n ContactUpdateInput,\n} from './types.js';\n\nfunction toQueryString(query: ContactListQuery): string {\n const params = new URLSearchParams();\n params.set('projectId', query.projectId);\n if (query.search) params.set('search', query.search);\n if (query.tags) {\n const tags = query.tags.filter(Boolean);\n if (tags.length > 0) params.set('tags', tags.join(','));\n }\n if (query.tagMode) params.set('tagMode', query.tagMode);\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.offset !== undefined) params.set('offset', String(query.offset));\n return params.toString();\n}\n\nexport class Contacts {\n constructor(private readonly http: HttpClient) {}\n\n async list(query: ContactListQuery): Promise<ContactListResponse> {\n return this.http.get<ContactListResponse>(\n `/contacts?${toQueryString(query)}`,\n );\n }\n\n async get(id: string): Promise<Contact> {\n return this.http.get<Contact>(`/contacts/${encodeURIComponent(id)}`);\n }\n\n async create(input: ContactCreateInput): Promise<Contact> {\n return this.http.post<Contact>('/contacts', input);\n }\n\n async update(id: string, input: ContactUpdateInput): Promise<Contact> {\n return this.http.patch<Contact>(\n `/contacts/${encodeURIComponent(id)}`,\n input,\n );\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete<void>(`/contacts/${encodeURIComponent(id)}`);\n }\n\n async bulkCreate(input: ContactBulkInput): Promise<ContactBulkResponse> {\n return this.http.post<ContactBulkResponse>('/contacts/bulk', input);\n }\n\n async tags(projectId: string): Promise<string[]> {\n const params = new URLSearchParams();\n params.set('projectId', projectId);\n return this.http.get<string[]>(`/contacts/tags?${params.toString()}`);\n }\n\n async lists(contactId: string): Promise<ContactListItem[]> {\n return this.http.get<ContactListItem[]>(\n `/contacts/${encodeURIComponent(contactId)}/lists`,\n );\n }\n\n /** @deprecated Use `lists.addContacts` instead. */\n async addToList(contactId: string, listId: string): Promise<void> {\n await this.http.post<{ added: number }>(\n `/lists/${encodeURIComponent(listId)}/contacts`,\n { contactIds: [contactId] },\n );\n }\n\n /** @deprecated Use `lists.removeContact` instead. */\n async removeFromList(contactId: string, listId: string): Promise<void> {\n await this.http.delete<void>(\n `/lists/${encodeURIComponent(listId)}/contacts/${encodeURIComponent(contactId)}`,\n );\n }\n}\n","import type { HttpClient } from './client.js';\nimport type {\n List,\n ListCreateInput,\n ListMembersQuery,\n ListMembersResponse,\n ListUpdateInput,\n ListWithCount,\n} from './types.js';\n\nfunction toQueryString(query: ListMembersQuery): string {\n const params = new URLSearchParams();\n if (query.search) params.set('search', query.search);\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.offset !== undefined) params.set('offset', String(query.offset));\n return params.toString();\n}\n\nexport class Lists {\n constructor(private readonly http: HttpClient) {}\n\n async list(projectId: string): Promise<ListWithCount[]> {\n const params = new URLSearchParams();\n params.set('projectId', projectId);\n return this.http.get<ListWithCount[]>(`/lists?${params.toString()}`);\n }\n\n async get(id: string): Promise<List> {\n return this.http.get<List>(`/lists/${encodeURIComponent(id)}`);\n }\n\n async create(input: ListCreateInput): Promise<List> {\n return this.http.post<List>('/lists', input);\n }\n\n async update(id: string, input: ListUpdateInput): Promise<List> {\n return this.http.patch<List>(`/lists/${encodeURIComponent(id)}`, input);\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete<void>(`/lists/${encodeURIComponent(id)}`);\n }\n\n async listContacts(\n id: string,\n query: ListMembersQuery = {},\n ): Promise<ListMembersResponse> {\n return this.http.get<ListMembersResponse>(\n `/lists/${encodeURIComponent(id)}/contacts?${toQueryString(query)}`,\n );\n }\n\n async addContacts(\n id: string,\n contactIds: string[],\n ): Promise<{ added: number }> {\n return this.http.post<{ added: number }>(\n `/lists/${encodeURIComponent(id)}/contacts`,\n { contactIds },\n );\n }\n\n async removeContact(id: string, contactId: string): Promise<void> {\n await this.http.delete<void>(\n `/lists/${encodeURIComponent(id)}/contacts/${encodeURIComponent(contactId)}`,\n );\n }\n}\n","import type { HttpClient } from './client.js';\nimport type {\n BatchSmsResponse,\n SendOptions,\n SendSmsInput,\n SmsListQuery,\n SmsListResponse,\n SmsMessageResponse,\n} from './types.js';\n\nfunction toQueryString(query: SmsListQuery): string {\n const params = new URLSearchParams();\n if (query.status) params.set('status', query.status);\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.cursor) params.set('cursor', query.cursor);\n return params.toString();\n}\n\nexport class Sms {\n constructor(private readonly http: HttpClient) {}\n\n async send(\n input: SendSmsInput,\n options?: SendOptions,\n ): Promise<SmsMessageResponse> {\n const key = options?.idempotencyKey ?? crypto.randomUUID();\n return this.http.post<SmsMessageResponse>('/v1/sms', input, {\n 'Idempotency-Key': key,\n });\n }\n\n async sendBatch(\n messages: SendSmsInput[],\n options?: SendOptions,\n ): Promise<BatchSmsResponse> {\n const key = options?.idempotencyKey ?? crypto.randomUUID();\n return this.http.post<BatchSmsResponse>(\n '/v1/sms/batch',\n { messages },\n { 'Idempotency-Key': key },\n );\n }\n\n async get(id: string): Promise<SmsMessageResponse> {\n return this.http.get<SmsMessageResponse>(\n `/v1/sms/${encodeURIComponent(id)}`,\n );\n }\n\n async list(query: SmsListQuery = {}): Promise<SmsListResponse> {\n const qs = toQueryString(query);\n return this.http.get<SmsListResponse>(`/v1/sms${qs ? `?${qs}` : ''}`);\n }\n}\n","export async function verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n): Promise<boolean> {\n const expected = signature.startsWith('sha256=')\n ? signature.slice(7)\n : signature;\n\n const enc = new TextEncoder();\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n enc.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n\n const mac = await globalThis.crypto.subtle.sign(\n 'HMAC',\n key,\n enc.encode(payload),\n );\n const computed = Array.from(new Uint8Array(mac))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n\n if (computed.length !== expected.length) return false;\n let diff = 0;\n for (let i = 0; i < computed.length; i++) {\n diff |= computed.charCodeAt(i) ^ expected.charCodeAt(i);\n }\n return diff === 0;\n}\n","/** Wire-format attachment shape — what actually goes over HTTP. */\nexport interface WireAttachmentInput {\n filename: string;\n content: string;\n contentType: string;\n}\n\n/** Ergonomic attachment input for `emails.send()` / `emails.batch()`. */\nexport interface AttachmentInput {\n filename: string;\n content: string | Uint8Array;\n contentType: string;\n}\n\n/**\n * Deliberately different from the API's `SendEmailInput` in `attachments`\n * only: this is the ergonomic pre-request shape while the API type is the\n * post-parse wire shape. Every other field matches exactly.\n */\nexport interface SendEmailInput {\n /**\n * Sender. Either a bare address (`noreply@example.com`) or a display-name\n * mailbox (`Acme <noreply@example.com>`). Names containing commas or\n * specials must be quoted (`\"Acme, Inc.\" <noreply@example.com>`); non-ASCII\n * names are encoded for you. The address domain must be verified.\n */\n from: string;\n to: string | string[];\n subject?: string;\n html?: string;\n text?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n template?: string;\n variables?: Record<string, string>;\n send_at?: string;\n attachments?: AttachmentInput[];\n /** RFC 5322 Message-ID (angle brackets included, e.g. `<id@host>`). */\n inReplyTo?: string;\n /** Same Message-ID format as `inReplyTo`, oldest first. */\n references?: string[];\n}\n\nexport interface SendOptions {\n idempotencyKey?: string;\n}\n\n/** Mirrors `apps/api/src/domains/emails/types.ts`'s server-side copy. */\nexport interface EmailResponse {\n id: string;\n status: string;\n duplicate?: boolean;\n}\n\nexport interface BatchEmailResultItem {\n index: number;\n id?: string;\n status?: string;\n duplicate?: boolean;\n error?: string;\n code?: string;\n}\n\nexport interface BatchEmailResponse {\n results: BatchEmailResultItem[];\n created: number;\n errors: number;\n limitReached?: boolean;\n}\n\n/** Every status an email can be in. */\nexport const EMAIL_STATUSES = [\n 'queued',\n 'rendering',\n 'submitted_to_postfix',\n 'deferred',\n 'sent',\n 'delivered',\n 'bounced',\n 'rejected',\n 'failed',\n 'suppressed',\n 'cancelled',\n 'scheduled',\n 'quarantined',\n 'sandboxed',\n] as const;\nexport type EmailStatus = (typeof EMAIL_STATUSES)[number];\n\n/** Every event type the server currently emits. */\nexport const EMAIL_EVENT_TYPES = [\n 'queued',\n 'rendering_started',\n 'rendering_completed',\n 'submitted_to_postfix',\n 'deferred',\n 'sent',\n 'delivered',\n 'bounced',\n 'rejected',\n 'failed',\n 'suppressed',\n 'cancelled',\n 'retry_scheduled',\n 'open',\n 'click',\n 'quarantined',\n] as const;\nexport type EmailEventType = (typeof EMAIL_EVENT_TYPES)[number];\n\n/** `metadata` shape for Postfix-derived lifecycle events. */\nexport interface DeliveryEventData {\n queueId: string;\n relay: string;\n response: string;\n timestamp: string;\n}\n","/** Mirrors `apps/api/src/domains/sms/repo.types.ts`'s status values. */\nexport const SMS_STATUSES = [\n 'queued',\n 'sending',\n 'sent',\n 'delivered',\n 'deferred',\n 'failed',\n 'suppressed',\n] as const;\nexport type SmsStatus = (typeof SMS_STATUSES)[number];\n/** Mirrors `apps/api/src/domains/sms/types.ts`'s `SendSmsInput`. */\nexport interface SendSmsInput {\n to: string;\n from?: string;\n body?: string;\n templateName?: string;\n variables?: Record<string, string>;\n tags?: string[];\n metadata?: Record<string, unknown>;\n}\nexport interface SmsMessageResponse {\n id: string;\n to: string;\n from: string | null;\n body: string;\n status: string;\n tags: string[];\n metadata: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\nexport interface SmsListResponse {\n data: SmsMessageResponse[];\n nextCursor: string | null;\n}\nexport interface SmsListQuery {\n status?: SmsStatus;\n limit?: number;\n cursor?: string;\n}\n\nexport interface BatchSmsResultItem {\n index: number;\n id?: string;\n status?: string;\n error?: string;\n code?: string;\n}\nexport interface BatchSmsResponse {\n results: BatchSmsResultItem[];\n created: number;\n errors: number;\n}\n"],"mappings":"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","names":["toQueryString","toQueryString","toQueryString"]}
package/dist/index.d.cts CHANGED
@@ -1,35 +1,135 @@
1
1
  interface DevelEmailConfig {
2
2
  apiKey: string;
3
3
  baseUrl: string;
4
+ maxRetries?: number;
4
5
  }
6
+
7
+ /** Wire-format attachment shape — what actually goes over HTTP. */
8
+ interface WireAttachmentInput {
9
+ filename: string;
10
+ content: string;
11
+ contentType: string;
12
+ }
13
+ /** Ergonomic attachment input for `emails.send()` / `emails.batch()`. */
14
+ interface AttachmentInput {
15
+ filename: string;
16
+ content: string | Uint8Array;
17
+ contentType: string;
18
+ }
19
+ /**
20
+ * Deliberately different from the API's `SendEmailInput` in `attachments`
21
+ * only: this is the ergonomic pre-request shape while the API type is the
22
+ * post-parse wire shape. Every other field matches exactly.
23
+ */
5
24
  interface SendEmailInput {
25
+ /**
26
+ * Sender. Either a bare address (`noreply@example.com`) or a display-name
27
+ * mailbox (`Acme <noreply@example.com>`). Names containing commas or
28
+ * specials must be quoted (`"Acme, Inc." <noreply@example.com>`); non-ASCII
29
+ * names are encoded for you. The address domain must be verified.
30
+ */
6
31
  from: string;
7
32
  to: string | string[];
8
- subject: string;
33
+ subject?: string;
9
34
  html?: string;
10
35
  text?: string;
11
36
  tags?: string[];
12
37
  metadata?: Record<string, unknown>;
38
+ template?: string;
39
+ variables?: Record<string, string>;
40
+ send_at?: string;
41
+ attachments?: AttachmentInput[];
42
+ /** RFC 5322 Message-ID (angle brackets included, e.g. `<id@host>`). */
43
+ inReplyTo?: string;
44
+ /** Same Message-ID format as `inReplyTo`, oldest first. */
45
+ references?: string[];
13
46
  }
47
+ interface SendOptions {
48
+ idempotencyKey?: string;
49
+ }
50
+ /** Mirrors `apps/api/src/domains/emails/types.ts`'s server-side copy. */
14
51
  interface EmailResponse {
15
52
  id: string;
16
53
  status: string;
54
+ duplicate?: boolean;
17
55
  }
18
- declare class DevelEmailError extends Error {
19
- readonly status: number;
20
- constructor(status: number, message: string);
56
+ interface BatchEmailResultItem {
57
+ index: number;
58
+ id?: string;
59
+ status?: string;
60
+ duplicate?: boolean;
61
+ error?: string;
62
+ code?: string;
21
63
  }
22
- declare class EmailsNamespace {
23
- private readonly config;
24
- constructor(config: DevelEmailConfig);
25
- send(input: SendEmailInput): Promise<EmailResponse>;
26
- get(id: string): Promise<EmailResponse>;
27
- private request;
64
+ interface BatchEmailResponse {
65
+ results: BatchEmailResultItem[];
66
+ created: number;
67
+ errors: number;
68
+ limitReached?: boolean;
28
69
  }
29
- declare class DevelEmail {
30
- readonly emails: EmailsNamespace;
31
- constructor(config: DevelEmailConfig);
70
+ /** Every status an email can be in. */
71
+ declare const EMAIL_STATUSES: readonly ["queued", "rendering", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "scheduled", "quarantined", "sandboxed"];
72
+ type EmailStatus = (typeof EMAIL_STATUSES)[number];
73
+ /** Every event type the server currently emits. */
74
+ declare const EMAIL_EVENT_TYPES: readonly ["queued", "rendering_started", "rendering_completed", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "retry_scheduled", "open", "click", "quarantined"];
75
+ type EmailEventType = (typeof EMAIL_EVENT_TYPES)[number];
76
+ /** `metadata` shape for Postfix-derived lifecycle events. */
77
+ interface DeliveryEventData {
78
+ queueId: string;
79
+ relay: string;
80
+ response: string;
81
+ timestamp: string;
32
82
  }
83
+
84
+ interface Domain {
85
+ id: string;
86
+ organizationId: string;
87
+ name: string;
88
+ verificationStatus: string;
89
+ spfStatus: string | null;
90
+ dkimStatus: string | null;
91
+ dmarcStatus: string | null;
92
+ ptrStatus: string | null;
93
+ mxStatus: string | null;
94
+ returnPathStatus: string | null;
95
+ dmarcPolicy: string | null;
96
+ dkimPublicKey: string;
97
+ dkimSelector: string;
98
+ createdAt: string;
99
+ updatedAt: string;
100
+ }
101
+ interface DomainCreateInput {
102
+ name: string;
103
+ dkimSelector?: string;
104
+ }
105
+
106
+ interface Template {
107
+ id: string;
108
+ organizationId: string;
109
+ name: string;
110
+ subject: string;
111
+ html: string;
112
+ text?: string | null;
113
+ variables?: string[];
114
+ createdAt: string;
115
+ updatedAt: string;
116
+ }
117
+ interface TemplateCreateInput {
118
+ name: string;
119
+ subject: string;
120
+ html: string;
121
+ text?: string;
122
+ variables?: string[];
123
+ }
124
+ interface TemplateUpdateInput {
125
+ name?: string;
126
+ subject?: string;
127
+ html?: string;
128
+ text?: string;
129
+ variables?: string[];
130
+ }
131
+
132
+ /** Mirrors `apps/api/src/domains/webhooks/types.ts`'s `WebhookPayload`. */
33
133
  interface WebhookEvent {
34
134
  event: string;
35
135
  emailId: string | null;
@@ -38,6 +138,354 @@ interface WebhookEvent {
38
138
  timestamp: string;
39
139
  metadata: Record<string, unknown>;
40
140
  }
141
+
142
+ type CampaignStatus = 'draft' | 'scheduled' | 'sending' | 'paused' | 'sent' | 'cancelled';
143
+ interface Campaign {
144
+ id: string;
145
+ projectId: string;
146
+ listId: string | null;
147
+ tagFilters: string[] | null;
148
+ tagFilterMode: 'and' | 'or' | null;
149
+ templateId: string | null;
150
+ name: string;
151
+ subject: string;
152
+ fromAddress: string;
153
+ fromName: string | null;
154
+ rawHtml: string | null;
155
+ rawText: string | null;
156
+ status: CampaignStatus;
157
+ scheduledFor: string | null;
158
+ sentAt: string | null;
159
+ totalRecipients: number | null;
160
+ summarizedStats: {
161
+ delivered?: number;
162
+ deliveryUnknown?: number;
163
+ opened?: number;
164
+ clicked?: number;
165
+ bounced?: number;
166
+ configurationBounced?: number;
167
+ unsubscribed?: number;
168
+ } | null;
169
+ createdAt: string;
170
+ updatedAt: string;
171
+ }
172
+ /** The SDK accepts optional pagination before the API applies defaults. */
173
+ interface CampaignListQuery {
174
+ projectId: string;
175
+ status?: CampaignStatus;
176
+ limit?: number;
177
+ offset?: number;
178
+ }
179
+ interface CampaignCreateInput {
180
+ projectId: string;
181
+ listId?: string;
182
+ tagFilters?: string[];
183
+ tagFilterMode?: 'and' | 'or';
184
+ templateId?: string;
185
+ name: string;
186
+ subject: string;
187
+ fromAddress: string;
188
+ fromName?: string;
189
+ rawHtml?: string;
190
+ rawText?: string;
191
+ scheduledFor?: string;
192
+ }
193
+ interface CampaignUpdateInput {
194
+ listId?: string | null;
195
+ tagFilters?: string[] | null;
196
+ tagFilterMode?: 'and' | 'or';
197
+ templateId?: string | null;
198
+ name?: string;
199
+ subject?: string;
200
+ fromAddress?: string;
201
+ fromName?: string;
202
+ rawHtml?: string;
203
+ rawText?: string;
204
+ scheduledFor?: string;
205
+ }
206
+ /** Mirrors `apps/api/src/domains/campaigns/types.ts`'s server-side copy. */
207
+ interface CampaignStats {
208
+ campaignId: string;
209
+ totalRecipients: number;
210
+ delivered: number;
211
+ deliveryUnknown: number;
212
+ opened: number;
213
+ clicked: number;
214
+ /** Reputation bounces only; configuration bounces are in `configurationBounced`. */
215
+ bounced: number;
216
+ configurationBounced: number;
217
+ unsubscribed: number;
218
+ openRate: number;
219
+ clickRate: number;
220
+ bounceRate: number;
221
+ links: Array<{
222
+ url: string;
223
+ clicks: number;
224
+ }>;
225
+ }
226
+ interface CampaignProviderStats {
227
+ provider: string;
228
+ opens: number;
229
+ clicks: number;
230
+ total: number;
231
+ }
232
+
233
+ interface Contact {
234
+ id: string;
235
+ projectId: string;
236
+ email: string;
237
+ firstName: string | null;
238
+ lastName: string | null;
239
+ country: string | null;
240
+ tags: string[];
241
+ unsubscribedAt: string | null;
242
+ createdAt: string;
243
+ updatedAt: string;
244
+ }
245
+ /** The SDK preserves optional filters before API parsing/defaulting. */
246
+ interface ContactListQuery {
247
+ projectId: string;
248
+ search?: string;
249
+ tags?: string[];
250
+ tagMode?: 'and' | 'or';
251
+ limit?: number;
252
+ offset?: number;
253
+ }
254
+ interface ContactListResponse {
255
+ data: Contact[];
256
+ total: number;
257
+ }
258
+ interface ContactCreateInput {
259
+ projectId: string;
260
+ email: string;
261
+ firstName?: string;
262
+ lastName?: string;
263
+ tags?: string[];
264
+ }
265
+ interface ContactUpdateInput {
266
+ unsubscribe: boolean;
267
+ }
268
+ interface ContactBulkContactInput {
269
+ email: string;
270
+ firstName?: string;
271
+ lastName?: string;
272
+ country?: string;
273
+ tags?: string[];
274
+ }
275
+ interface ContactBulkInput {
276
+ projectId: string;
277
+ contacts: ContactBulkContactInput[];
278
+ }
279
+ interface ContactBulkResponse {
280
+ inserted: number;
281
+ updated: number;
282
+ skipped: number;
283
+ }
284
+ /** `addedAt` is serialized to string for SDK consumers. */
285
+ interface ContactListItem {
286
+ id: string;
287
+ name: string;
288
+ memberCount: number;
289
+ addedAt: string;
290
+ }
291
+
292
+ interface List {
293
+ id: string;
294
+ projectId: string;
295
+ name: string;
296
+ description: string | null;
297
+ createdAt: string;
298
+ updatedAt: string;
299
+ }
300
+ /** API dates are serialized to strings for SDK consumers. */
301
+ interface ListWithCount extends List {
302
+ memberCount: number;
303
+ }
304
+ interface ListCreateInput {
305
+ projectId: string;
306
+ name: string;
307
+ description?: string;
308
+ }
309
+ interface ListUpdateInput {
310
+ name?: string;
311
+ description?: string;
312
+ }
313
+ interface ListMembersQuery {
314
+ search?: string;
315
+ limit?: number;
316
+ offset?: number;
317
+ }
318
+ interface ListMembersResponse {
319
+ data: Contact[];
320
+ total: number;
321
+ }
322
+
323
+ /** Mirrors `apps/api/src/domains/sms/repo.types.ts`'s status values. */
324
+ declare const SMS_STATUSES: readonly ["queued", "sending", "sent", "delivered", "deferred", "failed", "suppressed"];
325
+ type SmsStatus = (typeof SMS_STATUSES)[number];
326
+ /** Mirrors `apps/api/src/domains/sms/types.ts`'s `SendSmsInput`. */
327
+ interface SendSmsInput {
328
+ to: string;
329
+ from?: string;
330
+ body?: string;
331
+ templateName?: string;
332
+ variables?: Record<string, string>;
333
+ tags?: string[];
334
+ metadata?: Record<string, unknown>;
335
+ }
336
+ interface SmsMessageResponse {
337
+ id: string;
338
+ to: string;
339
+ from: string | null;
340
+ body: string;
341
+ status: string;
342
+ tags: string[];
343
+ metadata: Record<string, unknown>;
344
+ createdAt: string;
345
+ updatedAt: string;
346
+ }
347
+ interface SmsListResponse {
348
+ data: SmsMessageResponse[];
349
+ nextCursor: string | null;
350
+ }
351
+ interface SmsListQuery {
352
+ status?: SmsStatus;
353
+ limit?: number;
354
+ cursor?: string;
355
+ }
356
+ interface BatchSmsResultItem {
357
+ index: number;
358
+ id?: string;
359
+ status?: string;
360
+ error?: string;
361
+ code?: string;
362
+ }
363
+ interface BatchSmsResponse {
364
+ results: BatchSmsResultItem[];
365
+ created: number;
366
+ errors: number;
367
+ }
368
+
369
+ declare class HttpClient {
370
+ private readonly baseUrl;
371
+ private readonly apiKey;
372
+ private readonly maxRetries;
373
+ constructor(config: DevelEmailConfig);
374
+ get<T>(path: string): Promise<T>;
375
+ post<T>(path: string, body: unknown, extraHeaders?: Record<string, string>): Promise<T>;
376
+ put<T>(path: string, body: unknown, extraHeaders?: Record<string, string>): Promise<T>;
377
+ patch<T>(path: string, body: unknown, extraHeaders?: Record<string, string>): Promise<T>;
378
+ delete<T>(path: string, extraHeaders?: Record<string, string>): Promise<T>;
379
+ private request;
380
+ private backoff;
381
+ }
382
+
383
+ declare class Emails {
384
+ private readonly http;
385
+ constructor(http: HttpClient);
386
+ send(input: SendEmailInput, options?: SendOptions): Promise<EmailResponse>;
387
+ batch(emails: SendEmailInput[], options?: SendOptions): Promise<BatchEmailResponse>;
388
+ get(id: string): Promise<EmailResponse>;
389
+ }
390
+
391
+ declare class Domains {
392
+ private readonly http;
393
+ constructor(http: HttpClient);
394
+ list(): Promise<Domain[]>;
395
+ get(id: string): Promise<Domain>;
396
+ create(input: DomainCreateInput): Promise<Domain>;
397
+ delete(id: string): Promise<void>;
398
+ verify(id: string): Promise<Domain>;
399
+ }
400
+
401
+ declare class Templates {
402
+ private readonly http;
403
+ constructor(http: HttpClient);
404
+ list(): Promise<Template[]>;
405
+ get(id: string): Promise<Template>;
406
+ create(input: TemplateCreateInput): Promise<Template>;
407
+ update(id: string, input: TemplateUpdateInput): Promise<Template>;
408
+ delete(id: string): Promise<void>;
409
+ }
410
+
411
+ declare class Campaigns {
412
+ private readonly http;
413
+ constructor(http: HttpClient);
414
+ list(query: CampaignListQuery): Promise<Campaign[]>;
415
+ get(id: string): Promise<Campaign>;
416
+ create(input: CampaignCreateInput): Promise<Campaign>;
417
+ update(id: string, input: CampaignUpdateInput): Promise<Campaign>;
418
+ send(id: string): Promise<Campaign>;
419
+ pause(id: string): Promise<Campaign>;
420
+ resume(id: string): Promise<Campaign>;
421
+ cancel(id: string): Promise<Campaign & {
422
+ note: string;
423
+ }>;
424
+ duplicate(id: string): Promise<Campaign>;
425
+ delete(id: string): Promise<void>;
426
+ stats(id: string): Promise<CampaignStats>;
427
+ providerStats(id: string): Promise<CampaignProviderStats[]>;
428
+ }
429
+
430
+ declare class Contacts {
431
+ private readonly http;
432
+ constructor(http: HttpClient);
433
+ list(query: ContactListQuery): Promise<ContactListResponse>;
434
+ get(id: string): Promise<Contact>;
435
+ create(input: ContactCreateInput): Promise<Contact>;
436
+ update(id: string, input: ContactUpdateInput): Promise<Contact>;
437
+ delete(id: string): Promise<void>;
438
+ bulkCreate(input: ContactBulkInput): Promise<ContactBulkResponse>;
439
+ tags(projectId: string): Promise<string[]>;
440
+ lists(contactId: string): Promise<ContactListItem[]>;
441
+ /** @deprecated Use `lists.addContacts` instead. */
442
+ addToList(contactId: string, listId: string): Promise<void>;
443
+ /** @deprecated Use `lists.removeContact` instead. */
444
+ removeFromList(contactId: string, listId: string): Promise<void>;
445
+ }
446
+
447
+ declare class Lists {
448
+ private readonly http;
449
+ constructor(http: HttpClient);
450
+ list(projectId: string): Promise<ListWithCount[]>;
451
+ get(id: string): Promise<List>;
452
+ create(input: ListCreateInput): Promise<List>;
453
+ update(id: string, input: ListUpdateInput): Promise<List>;
454
+ delete(id: string): Promise<void>;
455
+ listContacts(id: string, query?: ListMembersQuery): Promise<ListMembersResponse>;
456
+ addContacts(id: string, contactIds: string[]): Promise<{
457
+ added: number;
458
+ }>;
459
+ removeContact(id: string, contactId: string): Promise<void>;
460
+ }
461
+
462
+ declare class Sms {
463
+ private readonly http;
464
+ constructor(http: HttpClient);
465
+ send(input: SendSmsInput, options?: SendOptions): Promise<SmsMessageResponse>;
466
+ sendBatch(messages: SendSmsInput[], options?: SendOptions): Promise<BatchSmsResponse>;
467
+ get(id: string): Promise<SmsMessageResponse>;
468
+ list(query?: SmsListQuery): Promise<SmsListResponse>;
469
+ }
470
+
471
+ declare class DevelEmailError extends Error {
472
+ readonly status: number;
473
+ readonly code?: string | undefined;
474
+ readonly details?: Record<string, unknown> | undefined;
475
+ constructor(status: number, message: string, code?: string | undefined, details?: Record<string, unknown> | undefined);
476
+ }
477
+
41
478
  declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
42
479
 
43
- export { DevelEmail, type DevelEmailConfig, DevelEmailError, type EmailResponse, type SendEmailInput, type WebhookEvent, verifyWebhookSignature };
480
+ declare class DevelEmail {
481
+ readonly emails: Emails;
482
+ readonly domains: Domains;
483
+ readonly templates: Templates;
484
+ readonly campaigns: Campaigns;
485
+ readonly contacts: Contacts;
486
+ readonly lists: Lists;
487
+ readonly sms: Sms;
488
+ constructor(config: DevelEmailConfig);
489
+ }
490
+
491
+ export { type AttachmentInput, type BatchEmailResponse, type BatchEmailResultItem, type BatchSmsResponse, type BatchSmsResultItem, type Campaign, type CampaignCreateInput, type CampaignListQuery, type CampaignProviderStats, type CampaignStats, type CampaignStatus, type CampaignUpdateInput, type Contact, type ContactBulkContactInput, type ContactBulkInput, type ContactBulkResponse, type ContactCreateInput, type ContactListItem, type ContactListQuery, type ContactListResponse, type ContactUpdateInput, type DeliveryEventData, DevelEmail, type DevelEmailConfig, DevelEmailError, type Domain, type DomainCreateInput, EMAIL_EVENT_TYPES, EMAIL_STATUSES, type EmailEventType, type EmailResponse, type EmailStatus, type List, type ListCreateInput, type ListMembersQuery, type ListMembersResponse, type ListUpdateInput, type ListWithCount, SMS_STATUSES, type SendEmailInput, type SendOptions, type SendSmsInput, type SmsListQuery, type SmsListResponse, type SmsMessageResponse, type SmsStatus, type Template, type TemplateCreateInput, type TemplateUpdateInput, type WebhookEvent, type WireAttachmentInput, verifyWebhookSignature };