mailumi 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mailumi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,225 @@
1
- # Temporary Holding Version
1
+ # mailumi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The official Node.js SDK and CLI for [Mailumi](https://mailumi.com), the email API for sending transactional email and campaigns from your own domain and receiving email as signed webhooks. Email content and account data are stored in the European Union, and email is sent through Amazon SES in Ireland.
4
+
5
+ - Typed, zero dependencies, ESM and CommonJS
6
+ - Runs on Node.js 18+, Bun, Deno, Cloudflare Workers, Vercel and any runtime with `fetch`
7
+ - Every method resolves to `{ data, error }` and never throws for API errors
8
+ - Automatic retries for rate limits and server errors; sends reuse one `Idempotency-Key`, so a retry never sends twice
9
+ - Webhook signature verification built in
10
+ - `npx mailumi send` from the terminal
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install mailumi
16
+ ```
17
+
18
+ Create an API key in [Dashboard → API keys](https://mailumi.com/dashboard/api-keys) and verify your domain in [Dashboard → Domains](https://mailumi.com/dashboard/domains). On Cloudflare, Mailumi adds the DNS records for you.
19
+
20
+ ```sh
21
+ MAILUMI_API_KEY=mlu_live_...
22
+ ```
23
+
24
+ ## Send an email
25
+
26
+ ```ts
27
+ import { Mailumi } from 'mailumi';
28
+
29
+ const mailumi = new Mailumi(process.env.MAILUMI_API_KEY);
30
+
31
+ const { data, error } = await mailumi.emails.send({
32
+ from: 'Acme <hello@yourdomain.com>',
33
+ to: 'jane@example.com',
34
+ subject: 'Welcome to Acme',
35
+ html: '<p>Thanks for signing up.</p>',
36
+ });
37
+
38
+ if (error) {
39
+ console.error(error.code, error.message);
40
+ } else {
41
+ console.log('Queued', data.id);
42
+ }
43
+ ```
44
+
45
+ With no argument, `new Mailumi()` reads `MAILUMI_API_KEY`. CommonJS works too: `const { Mailumi } = require('mailumi')`.
46
+
47
+ Only call Mailumi from server-side code. Never put your API key in browser or mobile code.
48
+
49
+ ## Switching from Resend
50
+
51
+ The method names and the `{ data, error }` result match the Resend SDK, so most code only needs a new import and API key:
52
+
53
+ ```diff
54
+ - import { Resend } from 'resend';
55
+ - const resend = new Resend(process.env.RESEND_API_KEY);
56
+ + import { Mailumi } from 'mailumi';
57
+ + const resend = new Mailumi(process.env.MAILUMI_API_KEY);
58
+
59
+ await resend.emails.send({ from, to, subject, html });
60
+ ```
61
+
62
+ Both `replyTo` and `reply_to` style options are accepted. Verify your domain in Mailumi before sending; the `from` address must use it.
63
+
64
+ ## More ways to send
65
+
66
+ ```ts
67
+ import { readFile } from 'node:fs/promises';
68
+
69
+ // Several recipients, cc, bcc and reply-to. Each recipient counts as one email.
70
+ await mailumi.emails.send({
71
+ from: 'Acme <billing@yourdomain.com>',
72
+ to: ['jane@example.com', 'sam@example.com'],
73
+ cc: 'finance@example.com',
74
+ replyTo: 'support@yourdomain.com',
75
+ subject: 'Your invoice',
76
+ text: 'Your invoice is attached.',
77
+ attachments: [{ filename: 'invoice.pdf', content: await readFile('invoice.pdf') }],
78
+ tags: [{ name: 'type', value: 'invoice' }],
79
+ });
80
+
81
+ // Schedule up to 30 days ahead: a Date, an ISO 8601 string, or "in 30 minutes", "in 2 hours", "in 3 days".
82
+ const { data } = await mailumi.emails.send({ from, to, subject, html, scheduledAt: 'in 3 days' });
83
+ await mailumi.emails.update({ id: data.id, scheduledAt: new Date('2030-01-01T09:00:00Z') });
84
+ await mailumi.emails.cancel(data.id);
85
+
86
+ // A template from Dashboard → Templates, with variables.
87
+ await mailumi.emails.send({ from, to, templateId: 'tpl_...', variables: { name: 'Jane' } });
88
+
89
+ // Up to 100 emails in one request. If one is invalid, none are sent.
90
+ await mailumi.batch.send([
91
+ { from, to: 'jane@example.com', subject: 'Hi Jane', text: '...' },
92
+ { from, to: 'sam@example.com', subject: 'Hi Sam', text: '...' },
93
+ ]);
94
+ ```
95
+
96
+ Attachment `content` can be a base64 string or bytes (`Buffer`, `Uint8Array`, `ArrayBuffer`). Show an image inline with `contentId: 'logo'` and `<img src="cid:logo">`.
97
+
98
+ ### Idempotency
99
+
100
+ Every send gets a random `Idempotency-Key`, reused on automatic retries. Pass your own to make a send safe to repeat across restarts or job retries:
101
+
102
+ ```ts
103
+ await mailumi.emails.send(email, { idempotencyKey: `order-confirmation/${order.id}` });
104
+ ```
105
+
106
+ Mailumi returns the original email for 24 hours. Using the same key for a different email returns a `conflict` error.
107
+
108
+ ## Delivery status and received email
109
+
110
+ ```ts
111
+ const { data: email } = await mailumi.emails.get(id);
112
+ email.status; // queued, sent, delivered, bounced, ...
113
+ email.recipients; // status per recipient
114
+ email.events; // delivered, opened, clicked, bounced, complained, ...
115
+
116
+ const { data: page } = await mailumi.emails.list({ direction: 'inbound' });
117
+ const { data: bytes } = await mailumi.emails.attachment({ id, index: 0 });
118
+ ```
119
+
120
+ ## Webhooks
121
+
122
+ Mailumi signs each webhook with the [Standard Webhooks](https://www.standardwebhooks.com/) format. `verifyWebhook` checks the signature and timestamp and returns the parsed event. It needs no API key; the secret defaults to `MAILUMI_WEBHOOK_SECRET`.
123
+
124
+ ```ts
125
+ import { verifyWebhook } from 'mailumi';
126
+
127
+ export async function POST(request: Request) {
128
+ const { data: event, error } = await verifyWebhook({
129
+ payload: await request.text(), // the raw body, before JSON parsing
130
+ headers: request.headers,
131
+ });
132
+ if (error) return new Response(error.message, { status: 401 });
133
+
134
+ if (event.type === 'email.received') {
135
+ // event.data has from, to, subject, text, html and attachments
136
+ }
137
+ return new Response('OK');
138
+ }
139
+ ```
140
+
141
+ `headers` can be a `Headers` object, Node's `req.headers`, or `{ id, timestamp, signature }`. Deliveries can repeat, so store `event.id` and skip ids you have seen. Events: `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.bounced`, `email.complained`, `email.opened`, `email.clicked`, `email.failed`, `email.unsubscribed` and `email.received`.
142
+
143
+ ## Audiences and contacts
144
+
145
+ Add contacts from your app for campaigns. Campaigns are written and sent in the dashboard, with an unsubscribe link and one-click unsubscribe in every email. These calls need an API key with the `contacts:write` permission.
146
+
147
+ ```ts
148
+ const { data: audience } = await mailumi.audiences.create({ name: 'Newsletter' });
149
+
150
+ await mailumi.contacts.create({ audienceId: audience.id, email: 'jane@example.com', firstName: 'Jane' });
151
+ await mailumi.contacts.import({ audienceId: audience.id, contacts: [{ email: 'sam@example.com' }, { email: 'lee@example.com' }] }); // up to 1,000 per call
152
+ await mailumi.contacts.update({ audienceId: audience.id, email: 'jane@example.com', unsubscribed: true });
153
+
154
+ const { data } = await mailumi.contacts.list({ audienceId: audience.id, search: 'example.com' });
155
+ ```
156
+
157
+ Unsubscribing applies to the address in every audience of the workspace, and adding or importing a contact never resubscribes it.
158
+
159
+ ## Errors
160
+
161
+ ```ts
162
+ const { error } = await mailumi.emails.send(email);
163
+ if (error) {
164
+ error.code; // 'invalid_request', 'unauthorized', 'plan_limit_reached', 'rate_limited', ...
165
+ error.message; // a readable explanation of what to fix
166
+ error.statusCode; // the HTTP status, or null for network errors
167
+ }
168
+ ```
169
+
170
+ | Code | Meaning |
171
+ | --- | --- |
172
+ | `invalid_request` | A field is missing or invalid; the message says which. |
173
+ | `unauthorized` | The API key is missing, wrong, revoked or expired. |
174
+ | `forbidden` | The key lacks the permission, or is limited to another domain. |
175
+ | `plan_limit_reached` | The monthly or daily email allowance is used up. |
176
+ | `not_found` | The email, domain, audience or contact does not exist. |
177
+ | `conflict` | The `Idempotency-Key` was used for a different request. |
178
+ | `rate_limited` | Too many requests; retried automatically. |
179
+ | `internal_error`, `service_unavailable` | A temporary problem; retried automatically. |
180
+ | `network_error` | Mailumi could not be reached or did not answer in time. |
181
+
182
+ ## Options
183
+
184
+ ```ts
185
+ new Mailumi(apiKey, {
186
+ baseUrl: 'https://mailumi.com/api/v1', // or MAILUMI_BASE_URL
187
+ maxRetries: 2, // retries for 429, 5xx and network errors
188
+ timeout: 30_000, // milliseconds per attempt
189
+ fetch, // a custom fetch implementation
190
+ });
191
+ ```
192
+
193
+ ## CLI
194
+
195
+ ```sh
196
+ export MAILUMI_API_KEY=mlu_live_...
197
+
198
+ npx mailumi send --from "Acme <hello@yourdomain.com>" --to jane@example.com \
199
+ --subject "Hello" --text "Sent from the terminal" --attach ./report.pdf
200
+
201
+ npx mailumi emails list
202
+ npx mailumi emails get <id>
203
+ npx mailumi emails cancel <id>
204
+ npx mailumi domains list
205
+ npx mailumi audiences list
206
+ npx mailumi contacts add <audience_id> jane@example.com --first-name Jane
207
+ ```
208
+
209
+ Add `--json` for machine-readable output and `--help` for every command and option. The CLI exits with code 1 on errors.
210
+
211
+ ## AI coding assistants
212
+
213
+ Mailumi publishes [llms.txt](https://mailumi.com/llms.txt), [llms-full.txt](https://mailumi.com/llms-full.txt), an [OpenAPI 3.1 description](https://mailumi.com/openapi.json) and an [MCP server](https://mailumi.com/docs#mcp), so Cursor, Claude Code, Codex and VS Code can send test emails and check delivery while they build your integration. See [mailumi.com/ai](https://mailumi.com/ai).
214
+
215
+ ## Links
216
+
217
+ - [Documentation](https://mailumi.com/docs)
218
+ - [Pricing](https://mailumi.com/pricing): the free plan includes 3,000 emails a month
219
+ - [Support](https://mailumi.com/contact)
220
+
221
+ ## Versioning
222
+
223
+ This package follows semantic versioning, as does the API: there are no breaking changes in v1. A breaking change only comes with a new major version, announced by email 6 months in advance, and v1 keeps working for at least 12 months after that.
224
+
225
+ MIT License
@@ -0,0 +1,306 @@
1
+ /**
2
+ * The official Mailumi SDK. Works anywhere `fetch` exists: Node.js 18+, Bun, Deno and Cloudflare Workers.
3
+ * Every method resolves to `{ data, error }` and never throws for API errors.
4
+ */
5
+ export declare const VERSION = "1.0.0";
6
+ export type ErrorCode = 'invalid_request' | 'unauthorized' | 'plan_limit_reached' | 'forbidden' | 'not_found' | 'method_not_allowed' | 'conflict' | 'payload_too_large' | 'rate_limited' | 'internal_error' | 'service_unavailable' | 'network_error' | 'missing_api_key' | 'invalid_signature' | 'missing_webhook_secret';
7
+ export interface MailumiError {
8
+ name: 'MailumiError';
9
+ code: ErrorCode | (string & {});
10
+ message: string;
11
+ statusCode: number | null;
12
+ }
13
+ export type Result<T> = {
14
+ data: T;
15
+ error: null;
16
+ } | {
17
+ data: null;
18
+ error: MailumiError;
19
+ };
20
+ export interface MailumiOptions {
21
+ /** Defaults to `https://mailumi.com/api/v1`. */
22
+ baseUrl?: string;
23
+ /** Retries for 429, 5xx and network errors. Sends reuse one Idempotency-Key, so a retry never sends twice. Other POSTs retry only on 429. Default 2. */
24
+ maxRetries?: number;
25
+ /** Milliseconds per attempt. Default 30000. */
26
+ timeout?: number;
27
+ /** A custom fetch implementation. */
28
+ fetch?: typeof fetch;
29
+ }
30
+ export interface RequestOptions {
31
+ idempotencyKey?: string;
32
+ }
33
+ export type Address = string;
34
+ export interface Attachment {
35
+ filename: string;
36
+ /** Base64 string, or raw bytes (Uint8Array, Buffer, ArrayBuffer), which are encoded for you. */
37
+ content: string | Uint8Array | ArrayBuffer;
38
+ contentType?: string;
39
+ /** Show the file inline in HTML with `<img src="cid:…">`. */
40
+ contentId?: string;
41
+ }
42
+ export interface Tag {
43
+ name: string;
44
+ value: string;
45
+ }
46
+ export interface SendEmailOptions {
47
+ from: Address;
48
+ to: Address | Address[];
49
+ cc?: Address | Address[];
50
+ bcc?: Address | Address[];
51
+ replyTo?: Address | Address[];
52
+ subject?: string;
53
+ html?: string;
54
+ text?: string;
55
+ attachments?: Attachment[];
56
+ headers?: Record<string, string>;
57
+ tags?: Tag[];
58
+ /** Up to 30 days ahead: a Date, an ISO 8601 string, or "in 30 minutes", "in 2 hours", "in 3 days". */
59
+ scheduledAt?: Date | string;
60
+ /** Send a template from Dashboard → Templates instead of subject and html. */
61
+ templateId?: string;
62
+ variables?: Record<string, string>;
63
+ }
64
+ export interface Email {
65
+ id: string;
66
+ object: 'email';
67
+ domain_id?: string;
68
+ direction: 'outbound' | 'inbound';
69
+ from: string;
70
+ to: string[];
71
+ subject: string;
72
+ status: string;
73
+ last_event?: string;
74
+ scheduled_at: string | null;
75
+ tags?: Record<string, string>;
76
+ error: string | null;
77
+ created_at: string;
78
+ }
79
+ export interface EmailDetail extends Email {
80
+ cc?: string[];
81
+ bcc?: string[];
82
+ reply_to?: string[];
83
+ text?: string;
84
+ html?: string;
85
+ headers?: Record<string, string>;
86
+ content_deleted?: boolean;
87
+ recipients?: {
88
+ recipient: string;
89
+ kind: 'to' | 'cc' | 'bcc';
90
+ status: string;
91
+ }[];
92
+ events?: {
93
+ type: string;
94
+ recipient: string | null;
95
+ created_at: string;
96
+ [key: string]: unknown;
97
+ }[];
98
+ attachments?: {
99
+ id: string;
100
+ filename: string;
101
+ content_type: string;
102
+ content_id: string | null;
103
+ size: number;
104
+ download_url: string;
105
+ }[];
106
+ }
107
+ export interface EmailList {
108
+ data: Email[];
109
+ next_cursor: string | null;
110
+ }
111
+ export interface DnsRecord {
112
+ purpose: 'ownership' | 'dkim' | 'mail_from' | 'dmarc' | 'receiving';
113
+ required: boolean;
114
+ type: 'TXT' | 'CNAME' | 'MX';
115
+ name: string;
116
+ value: string;
117
+ priority?: number;
118
+ verified: boolean;
119
+ }
120
+ export interface Domain {
121
+ id: string;
122
+ name: string;
123
+ ownershipVerified?: boolean;
124
+ sendingEnabled?: boolean;
125
+ receivingEnabled?: boolean;
126
+ openTracking?: boolean;
127
+ clickTracking?: boolean;
128
+ sendingStatus: string;
129
+ receivingStatus?: string;
130
+ lastChecked?: number | null;
131
+ createdAt?: number;
132
+ records: DnsRecord[];
133
+ }
134
+ export interface Audience {
135
+ object: 'audience';
136
+ id: string;
137
+ name: string;
138
+ contacts: number;
139
+ subscribed: number;
140
+ created_at: string;
141
+ }
142
+ export interface Contact {
143
+ object: 'contact';
144
+ id: string;
145
+ audience_id: string;
146
+ email: string;
147
+ first_name: string;
148
+ last_name: string;
149
+ unsubscribed: boolean;
150
+ unsubscribed_at: string | null;
151
+ created_at: string;
152
+ updated_at: string;
153
+ }
154
+ export interface NewContact {
155
+ email: string;
156
+ firstName?: string;
157
+ lastName?: string;
158
+ unsubscribed?: boolean;
159
+ }
160
+ export interface ContactImport {
161
+ object: 'import';
162
+ audience_id: string;
163
+ added: number;
164
+ updated: number;
165
+ invalid: {
166
+ index: number;
167
+ email: string;
168
+ error: string;
169
+ }[];
170
+ }
171
+ export interface Deleted {
172
+ object: string;
173
+ id: string;
174
+ deleted: true;
175
+ }
176
+ type ContactRef = {
177
+ audienceId: string;
178
+ } & ({
179
+ id: string;
180
+ email?: never;
181
+ } | {
182
+ email: string;
183
+ id?: never;
184
+ });
185
+ export type WebhookEventType = 'email.sent' | 'email.delivered' | 'email.delivery_delayed' | 'email.bounced' | 'email.complained' | 'email.opened' | 'email.clicked' | 'email.failed' | 'email.unsubscribed' | 'email.received' | 'webhook.test';
186
+ export interface WebhookEvent<T = Record<string, any>> {
187
+ id: string;
188
+ type: WebhookEventType | (string & {});
189
+ created_at: string;
190
+ data: T;
191
+ }
192
+ export interface VerifyWebhookOptions {
193
+ /** The raw request body exactly as received, before any JSON parsing. */
194
+ payload: string | Uint8Array;
195
+ /** A Headers object, Node's `req.headers`, or `{ id, timestamp, signature }`. */
196
+ headers: Headers | Record<string, string | string[] | undefined>;
197
+ /** The endpoint's `whsec_…` secret. Defaults to the MAILUMI_WEBHOOK_SECRET environment variable. */
198
+ secret?: string;
199
+ /** How many seconds the timestamp may differ from now. Default 300. */
200
+ tolerance?: number;
201
+ }
202
+ /**
203
+ * Checks the Standard Webhooks signature of a Mailumi webhook and returns the parsed event.
204
+ * Store `event.id` and skip ids you have seen: a delivery can arrive more than once.
205
+ */
206
+ export declare function verifyWebhook<T = Record<string, any>>({ payload, headers, secret, tolerance }: VerifyWebhookOptions): Promise<Result<WebhookEvent<T>>>;
207
+ /** Converts the SDK's camelCase options to the API's request body. snake_case keys are accepted too. */
208
+ export declare function toApiEmail(o: SendEmailOptions & Record<string, any>): Record<string, unknown>;
209
+ export declare class Mailumi {
210
+ readonly emails: {
211
+ /** Sends one email. Each recipient counts as one email toward your plan. */
212
+ send: (email: SendEmailOptions, o?: RequestOptions) => Promise<Result<Email>>;
213
+ /** Lists sent and received email, newest first, 50 per page. */
214
+ list: (q?: {
215
+ direction?: "outbound" | "inbound";
216
+ before?: string;
217
+ }) => Promise<Result<EmailList>>;
218
+ /** Returns the content, per-recipient status, events and attachment links. */
219
+ get: (id: string) => Promise<Result<EmailDetail>>;
220
+ /** Changes the send time of a scheduled email. */
221
+ update: ({ id, scheduledAt }: {
222
+ id: string;
223
+ scheduledAt: Date | string;
224
+ }) => Promise<Result<Email>>;
225
+ /** Cancels a scheduled email and returns it to your plan allowance. */
226
+ cancel: (id: string) => Promise<Result<{
227
+ object: "email";
228
+ id: string;
229
+ status: "canceled";
230
+ }>>;
231
+ /** Downloads one attachment of a sent or received email as bytes. */
232
+ attachment: ({ id, index }: {
233
+ id: string;
234
+ index: number | string;
235
+ }) => Promise<Result<Uint8Array<ArrayBufferLike>>>;
236
+ };
237
+ readonly batch: {
238
+ /** Sends up to 100 emails. If one is invalid, none are sent. */
239
+ send: (emails: SendEmailOptions[], o?: RequestOptions) => Promise<Result<{
240
+ data: {
241
+ id: string;
242
+ }[];
243
+ }>>;
244
+ };
245
+ readonly domains: {
246
+ /** Lists your domains, their DNS records and whether each is verified. */
247
+ list: () => Promise<Result<{
248
+ data: Domain[];
249
+ }>>;
250
+ };
251
+ readonly audiences: {
252
+ list: () => Promise<Result<{
253
+ data: Audience[];
254
+ }>>;
255
+ create: ({ name }: {
256
+ name: string;
257
+ }) => Promise<Result<Audience>>;
258
+ get: (id: string) => Promise<Result<Audience>>;
259
+ update: ({ id, name }: {
260
+ id: string;
261
+ name: string;
262
+ }) => Promise<Result<Audience>>;
263
+ /** Deletes the audience and its contacts. */
264
+ remove: (id: string) => Promise<Result<Deleted>>;
265
+ };
266
+ readonly contacts: {
267
+ /** Adds one contact, or updates its name if it exists. Importing never resubscribes anyone. */
268
+ create: ({ audienceId, ...contact }: NewContact & {
269
+ audienceId: string;
270
+ }) => Promise<Result<Contact>>;
271
+ /** Imports up to 1,000 contacts in one request. */
272
+ import: ({ audienceId, contacts }: {
273
+ audienceId: string;
274
+ contacts: NewContact[];
275
+ }) => Promise<Result<ContactImport>>;
276
+ /** Lists contacts sorted by email, 100 per page. */
277
+ list: ({ audienceId, after, search }: {
278
+ audienceId: string;
279
+ after?: string;
280
+ search?: string;
281
+ }) => Promise<Result<{
282
+ data: Contact[];
283
+ next_cursor: string | null;
284
+ }>>;
285
+ get: (ref: ContactRef) => Promise<Result<Contact>>;
286
+ /** Unsubscribing applies to the address in every audience of the workspace. */
287
+ update: ({ firstName, lastName, unsubscribed, ...ref }: ContactRef & {
288
+ firstName?: string;
289
+ lastName?: string;
290
+ unsubscribed?: boolean;
291
+ }) => Promise<Result<Contact>>;
292
+ remove: (ref: ContactRef) => Promise<Result<Deleted>>;
293
+ };
294
+ /** Verifies a webhook signature. Needs no API key; `verifyWebhook` is also exported on its own. */
295
+ readonly webhooks: {
296
+ verify: typeof verifyWebhook;
297
+ };
298
+ private readonly key;
299
+ private readonly baseUrl;
300
+ private readonly maxRetries;
301
+ private readonly timeout;
302
+ private readonly fetcher;
303
+ constructor(apiKey?: string, options?: MailumiOptions);
304
+ private request;
305
+ }
306
+ export default Mailumi;