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 +21 -0
- package/README.md +224 -2
- package/dist/cjs/index.d.ts +306 -0
- package/dist/cjs/index.js +202 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +174 -0
- package/dist/index.d.ts +306 -0
- package/dist/index.js +196 -0
- package/package.json +58 -4
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
|
-
#
|
|
1
|
+
# mailumi
|
|
2
2
|
|
|
3
|
-
|
|
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;
|