mailtea-sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/index.d.ts +960 -0
- package/dist/index.js +671 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
// src/resource.ts
|
|
2
|
+
function query(params) {
|
|
3
|
+
const entries = Object.entries(params).filter(
|
|
4
|
+
([, v]) => v !== void 0 && v !== null && v !== ""
|
|
5
|
+
);
|
|
6
|
+
if (entries.length === 0) return "";
|
|
7
|
+
return "?" + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/inbound.ts
|
|
11
|
+
var InboundAttachments = class {
|
|
12
|
+
constructor(request) {
|
|
13
|
+
this.request = request;
|
|
14
|
+
}
|
|
15
|
+
/** List an inbound email's attachments, each with a signed download URL. */
|
|
16
|
+
list(id) {
|
|
17
|
+
return this.request(
|
|
18
|
+
"GET",
|
|
19
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/attachments`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
/** Retrieve a single inbound attachment with a signed download URL. */
|
|
23
|
+
get(id, attachmentId) {
|
|
24
|
+
return this.request(
|
|
25
|
+
"GET",
|
|
26
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/attachments/${encodeURIComponent(attachmentId)}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var InboundEmails = class {
|
|
31
|
+
constructor(request) {
|
|
32
|
+
this.request = request;
|
|
33
|
+
this.attachments = new InboundAttachments(request);
|
|
34
|
+
}
|
|
35
|
+
/** Attachments on a received email. */
|
|
36
|
+
attachments;
|
|
37
|
+
/** List received emails in a publication (most recent first), cursor-paginated. */
|
|
38
|
+
list(params) {
|
|
39
|
+
return this.request(
|
|
40
|
+
"GET",
|
|
41
|
+
`/v1/emails/inbound${query({ ...params })}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
/** Retrieve a single received email, including its body, headers, and attachments. */
|
|
45
|
+
get(id) {
|
|
46
|
+
return this.request(
|
|
47
|
+
"GET",
|
|
48
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reply to a received email. The reply target, threading headers, and the
|
|
53
|
+
* `Re: ` subject default are all server-derived — pass only the content.
|
|
54
|
+
*
|
|
55
|
+
* @returns the resulting transactional email's `id` and `status` (HTTP 202).
|
|
56
|
+
*/
|
|
57
|
+
reply(id, input) {
|
|
58
|
+
return this.request(
|
|
59
|
+
"POST",
|
|
60
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/reply`,
|
|
61
|
+
input
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/emails.ts
|
|
67
|
+
var Emails = class {
|
|
68
|
+
constructor(request) {
|
|
69
|
+
this.request = request;
|
|
70
|
+
this.inbound = new InboundEmails(request);
|
|
71
|
+
}
|
|
72
|
+
/** Inbound (received) emails: list, get, reply, and attachments. */
|
|
73
|
+
inbound;
|
|
74
|
+
/**
|
|
75
|
+
* Send a transactional email.
|
|
76
|
+
*
|
|
77
|
+
* Provide either inline content (`html` and/or `text`) **or** a `template`
|
|
78
|
+
* reference — not both. `to`, `cc`, `bcc`, and `reply_to` each accept a single
|
|
79
|
+
* address or an array.
|
|
80
|
+
*
|
|
81
|
+
* @returns the new email's `id`.
|
|
82
|
+
*/
|
|
83
|
+
async send(payload) {
|
|
84
|
+
return this.request("POST", "/v1/emails", payload);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Send up to 100 emails in a single request. Batch items do not support
|
|
88
|
+
* `attachments` or `scheduled_at`.
|
|
89
|
+
*
|
|
90
|
+
* @returns `{ data: [{ id }, ...] }` in request order.
|
|
91
|
+
*/
|
|
92
|
+
async batch(payload) {
|
|
93
|
+
return this.request(
|
|
94
|
+
"POST",
|
|
95
|
+
"/v1/emails/batch",
|
|
96
|
+
payload
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* List transactional emails (most recent first), optionally filtered by
|
|
101
|
+
* status, tags, or a `created_at` date range, and offset-paginated.
|
|
102
|
+
*
|
|
103
|
+
* @returns `{ data, total, limit, offset, has_more }`.
|
|
104
|
+
*/
|
|
105
|
+
async list(params = {}) {
|
|
106
|
+
return this.request("GET", `/v1/emails${query({ ...params })}`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Aggregate transactional metrics over an optional date window: totals,
|
|
110
|
+
* delivered/bounced/open/click counts, per-status counts, and rates.
|
|
111
|
+
*/
|
|
112
|
+
async analytics(params = {}) {
|
|
113
|
+
return this.request("GET", `/v1/emails/analytics${query({ ...params })}`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Retrieve a single email with its current delivery status and tracking
|
|
117
|
+
* counters. The returned object exposes both `last_event` (the raw wire field)
|
|
118
|
+
* and a friendly `status` alias.
|
|
119
|
+
*/
|
|
120
|
+
async get(id) {
|
|
121
|
+
const email = await this.request(
|
|
122
|
+
"GET",
|
|
123
|
+
`/v1/emails/${encodeURIComponent(id)}`
|
|
124
|
+
);
|
|
125
|
+
return { ...email, status: email.status ?? email.last_event ?? null };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Update a scheduled email — currently only its `scheduled_at`. Only emails
|
|
129
|
+
* still in the `scheduled` state can be updated.
|
|
130
|
+
*/
|
|
131
|
+
async update(id, payload) {
|
|
132
|
+
return this.request(
|
|
133
|
+
"PATCH",
|
|
134
|
+
`/v1/emails/${encodeURIComponent(id)}`,
|
|
135
|
+
payload
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Convenience wrapper over {@link Emails.update} for the common reschedule
|
|
140
|
+
* case.
|
|
141
|
+
*
|
|
142
|
+
* @param scheduledAt ISO 8601 datetime string.
|
|
143
|
+
*/
|
|
144
|
+
async reschedule(id, scheduledAt) {
|
|
145
|
+
return this.update(id, { scheduled_at: scheduledAt });
|
|
146
|
+
}
|
|
147
|
+
/** Cancel a scheduled email before it sends. */
|
|
148
|
+
async cancel(id) {
|
|
149
|
+
return this.request(
|
|
150
|
+
"POST",
|
|
151
|
+
`/v1/emails/${encodeURIComponent(id)}/cancel`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/contacts.ts
|
|
157
|
+
var Contacts = class {
|
|
158
|
+
constructor(request) {
|
|
159
|
+
this.request = request;
|
|
160
|
+
}
|
|
161
|
+
/** Create (or upsert) a contact in a publication. */
|
|
162
|
+
create(input) {
|
|
163
|
+
return this.request("POST", "/v1/contacts", input);
|
|
164
|
+
}
|
|
165
|
+
/** List contacts in a publication. Supports cursor pagination via `after`. */
|
|
166
|
+
list(params) {
|
|
167
|
+
return this.request(
|
|
168
|
+
"GET",
|
|
169
|
+
`/v1/contacts${query({ ...params })}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
/** Retrieve a single contact by id or email. */
|
|
173
|
+
get(idOrEmail, params) {
|
|
174
|
+
return this.request(
|
|
175
|
+
"GET",
|
|
176
|
+
`/v1/contacts/${encodeURIComponent(idOrEmail)}${query({ ...params })}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
/** Update a contact's status. */
|
|
180
|
+
update(idOrEmail, input) {
|
|
181
|
+
return this.request(
|
|
182
|
+
"PATCH",
|
|
183
|
+
`/v1/contacts/${encodeURIComponent(idOrEmail)}${query({ publication_id: input.publication_id })}`,
|
|
184
|
+
input
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
/** Delete a contact by id or email. */
|
|
188
|
+
delete(idOrEmail, params) {
|
|
189
|
+
return this.request(
|
|
190
|
+
"DELETE",
|
|
191
|
+
`/v1/contacts/${encodeURIComponent(idOrEmail)}${query({ ...params })}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/segments.ts
|
|
197
|
+
var Segments = class {
|
|
198
|
+
constructor(request) {
|
|
199
|
+
this.request = request;
|
|
200
|
+
}
|
|
201
|
+
/** Create a segment. */
|
|
202
|
+
create(input) {
|
|
203
|
+
return this.request("POST", "/v1/segments", input);
|
|
204
|
+
}
|
|
205
|
+
/** List segments in a publication. */
|
|
206
|
+
list(params) {
|
|
207
|
+
return this.request(
|
|
208
|
+
"GET",
|
|
209
|
+
`/v1/segments${query({ ...params })}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
/** Retrieve a single segment. */
|
|
213
|
+
get(id, params) {
|
|
214
|
+
return this.request(
|
|
215
|
+
"GET",
|
|
216
|
+
`/v1/segments/${encodeURIComponent(id)}${query({ ...params })}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
/** Update a segment. */
|
|
220
|
+
update(id, input) {
|
|
221
|
+
return this.request(
|
|
222
|
+
"PATCH",
|
|
223
|
+
`/v1/segments/${encodeURIComponent(id)}${query({ publication_id: input.publication_id })}`,
|
|
224
|
+
input
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
/** Delete a segment. */
|
|
228
|
+
delete(id, params) {
|
|
229
|
+
return this.request(
|
|
230
|
+
"DELETE",
|
|
231
|
+
`/v1/segments/${encodeURIComponent(id)}${query({ ...params })}`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/tags.ts
|
|
237
|
+
var Tags = class {
|
|
238
|
+
constructor(request) {
|
|
239
|
+
this.request = request;
|
|
240
|
+
}
|
|
241
|
+
/** Create a tag. */
|
|
242
|
+
create(input) {
|
|
243
|
+
return this.request("POST", "/v1/tags", input);
|
|
244
|
+
}
|
|
245
|
+
/** List tags in a publication. */
|
|
246
|
+
list(params) {
|
|
247
|
+
return this.request(
|
|
248
|
+
"GET",
|
|
249
|
+
`/v1/tags${query({ ...params })}`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
/** Retrieve a single tag. */
|
|
253
|
+
get(id, params) {
|
|
254
|
+
return this.request(
|
|
255
|
+
"GET",
|
|
256
|
+
`/v1/tags/${encodeURIComponent(id)}${query({ ...params })}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
/** Update a tag. */
|
|
260
|
+
update(id, input) {
|
|
261
|
+
return this.request(
|
|
262
|
+
"PATCH",
|
|
263
|
+
`/v1/tags/${encodeURIComponent(id)}${query({ publication_id: input.publication_id })}`,
|
|
264
|
+
input
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
/** Delete a tag. */
|
|
268
|
+
delete(id, params) {
|
|
269
|
+
return this.request(
|
|
270
|
+
"DELETE",
|
|
271
|
+
`/v1/tags/${encodeURIComponent(id)}${query({ ...params })}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/posts.ts
|
|
277
|
+
var Posts = class {
|
|
278
|
+
constructor(request) {
|
|
279
|
+
this.request = request;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Create a newsletter post (draft by default). Seed it from a published
|
|
283
|
+
* server template with `template_id` + `variables`, or pass inline `html`.
|
|
284
|
+
* Set `send: true` to deliver immediately (or with `scheduled_at` to
|
|
285
|
+
* schedule) — that requires the `issues:send` scope. Returns `{ id }`.
|
|
286
|
+
*/
|
|
287
|
+
create(input) {
|
|
288
|
+
return this.request("POST", "/v1/posts", input);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Send a TEST copy of a post to specific recipients to check it before
|
|
292
|
+
* subscribers see it. Renders the post exactly as a subscriber would receive
|
|
293
|
+
* it and delivers a one-shot `[TEST]` email — it does NOT send to the
|
|
294
|
+
* audience. Returns `{ sent_to, failed_to }`.
|
|
295
|
+
*/
|
|
296
|
+
sendTest(id, input) {
|
|
297
|
+
return this.request(
|
|
298
|
+
"POST",
|
|
299
|
+
`/v1/posts/${encodeURIComponent(id)}/test`,
|
|
300
|
+
input
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/domains.ts
|
|
306
|
+
var TrackingDomains = class {
|
|
307
|
+
constructor(request) {
|
|
308
|
+
this.request = request;
|
|
309
|
+
}
|
|
310
|
+
/** Add a tracking sub-domain. The response `records` lists the CNAME to add. */
|
|
311
|
+
create(domainId, input) {
|
|
312
|
+
return this.request(
|
|
313
|
+
"POST",
|
|
314
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains${query({ publication_id: input.publication_id })}`,
|
|
315
|
+
{ subdomain: input.subdomain }
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
/** List tracking sub-domains for a domain. */
|
|
319
|
+
list(domainId, params) {
|
|
320
|
+
return this.request(
|
|
321
|
+
"GET",
|
|
322
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains${query({ ...params })}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
/** Verify a tracking sub-domain by checking its CNAME record. */
|
|
326
|
+
verify(domainId, trackingDomainId, params) {
|
|
327
|
+
return this.request(
|
|
328
|
+
"POST",
|
|
329
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains/${encodeURIComponent(trackingDomainId)}/verify${query({ ...params })}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
/** Delete a tracking sub-domain. */
|
|
333
|
+
delete(domainId, trackingDomainId, params) {
|
|
334
|
+
return this.request(
|
|
335
|
+
"DELETE",
|
|
336
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains/${encodeURIComponent(trackingDomainId)}${query({ ...params })}`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var Domains = class {
|
|
341
|
+
constructor(request) {
|
|
342
|
+
this.request = request;
|
|
343
|
+
this.tracking = new TrackingDomains(request);
|
|
344
|
+
}
|
|
345
|
+
/** Tracking sub-domains (CNAME) under a domain. */
|
|
346
|
+
tracking;
|
|
347
|
+
/** Register a domain. The response `records` lists the DNS records to add. */
|
|
348
|
+
create(input) {
|
|
349
|
+
return this.request("POST", "/v1/domains", input);
|
|
350
|
+
}
|
|
351
|
+
/** List domains in a publication. */
|
|
352
|
+
list(params) {
|
|
353
|
+
return this.request(
|
|
354
|
+
"GET",
|
|
355
|
+
`/v1/domains${query({ ...params })}`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
/** Retrieve a single domain, including its DNS `records`. */
|
|
359
|
+
get(id, params) {
|
|
360
|
+
return this.request(
|
|
361
|
+
"GET",
|
|
362
|
+
`/v1/domains/${encodeURIComponent(id)}${query({ ...params })}`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
/** Verify a domain by checking its DNS records; `status` becomes `verified`. */
|
|
366
|
+
verify(id, params) {
|
|
367
|
+
return this.request(
|
|
368
|
+
"POST",
|
|
369
|
+
`/v1/domains/${encodeURIComponent(id)}/verify${query({ ...params })}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
/** Update a domain's purpose, primary flag, or proxy target. */
|
|
373
|
+
update(id, input) {
|
|
374
|
+
return this.request(
|
|
375
|
+
"PATCH",
|
|
376
|
+
`/v1/domains/${encodeURIComponent(id)}${query({ publication_id: input.publication_id })}`,
|
|
377
|
+
input
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
/** Delete a domain. */
|
|
381
|
+
delete(id, params) {
|
|
382
|
+
return this.request(
|
|
383
|
+
"DELETE",
|
|
384
|
+
`/v1/domains/${encodeURIComponent(id)}${query({ ...params })}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/webhooks.ts
|
|
390
|
+
var BASE = "/v1/webhooks/endpoints";
|
|
391
|
+
var Webhooks = class {
|
|
392
|
+
constructor(request) {
|
|
393
|
+
this.request = request;
|
|
394
|
+
}
|
|
395
|
+
/** Create a webhook. The response `signing_secret` is returned only once. */
|
|
396
|
+
create(input) {
|
|
397
|
+
return this.request("POST", BASE, input);
|
|
398
|
+
}
|
|
399
|
+
/** List webhooks in a publication (signing secrets omitted). */
|
|
400
|
+
list(params) {
|
|
401
|
+
return this.request(
|
|
402
|
+
"GET",
|
|
403
|
+
`${BASE}${query({ ...params })}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
/** Retrieve a single webhook. */
|
|
407
|
+
get(id, params) {
|
|
408
|
+
return this.request(
|
|
409
|
+
"GET",
|
|
410
|
+
`${BASE}/${encodeURIComponent(id)}${query({ ...params })}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
/** Update a webhook's endpoint, events, or enabled/disabled status. */
|
|
414
|
+
update(id, input) {
|
|
415
|
+
return this.request(
|
|
416
|
+
"PATCH",
|
|
417
|
+
`${BASE}/${encodeURIComponent(id)}${query({ publication_id: input.publication_id })}`,
|
|
418
|
+
input
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
/** Delete a webhook. */
|
|
422
|
+
delete(id, params) {
|
|
423
|
+
return this.request(
|
|
424
|
+
"DELETE",
|
|
425
|
+
`${BASE}/${encodeURIComponent(id)}${query({ ...params })}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/contact-properties.ts
|
|
431
|
+
var ContactProperties = class {
|
|
432
|
+
constructor(request) {
|
|
433
|
+
this.request = request;
|
|
434
|
+
}
|
|
435
|
+
/** Define a custom contact property. */
|
|
436
|
+
create(input) {
|
|
437
|
+
return this.request("POST", "/v1/contact-properties", input);
|
|
438
|
+
}
|
|
439
|
+
/** List the team's custom contact property definitions. */
|
|
440
|
+
list(params = {}) {
|
|
441
|
+
return this.request(
|
|
442
|
+
"GET",
|
|
443
|
+
`/v1/contact-properties${query({ ...params })}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
/** Update a property's fallback value or description. */
|
|
447
|
+
update(id, input) {
|
|
448
|
+
return this.request(
|
|
449
|
+
"PATCH",
|
|
450
|
+
`/v1/contact-properties/${encodeURIComponent(id)}`,
|
|
451
|
+
input
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
/** Delete a custom contact property. */
|
|
455
|
+
delete(id) {
|
|
456
|
+
return this.request(
|
|
457
|
+
"DELETE",
|
|
458
|
+
`/v1/contact-properties/${encodeURIComponent(id)}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// src/api-keys.ts
|
|
464
|
+
var ApiKeys = class {
|
|
465
|
+
constructor(request) {
|
|
466
|
+
this.request = request;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Create an API key. The `token` is returned ONCE — store it securely. The
|
|
470
|
+
* calling token must already hold every scope the new key would grant.
|
|
471
|
+
*/
|
|
472
|
+
create(input) {
|
|
473
|
+
return this.request("POST", "/v1/api-keys", input);
|
|
474
|
+
}
|
|
475
|
+
/** List API keys (token values are never returned). */
|
|
476
|
+
list() {
|
|
477
|
+
return this.request("GET", "/v1/api-keys");
|
|
478
|
+
}
|
|
479
|
+
/** Revoke (delete) an API key by id. */
|
|
480
|
+
revoke(id) {
|
|
481
|
+
return this.request("DELETE", `/v1/api-keys/${encodeURIComponent(id)}`);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// src/errors.ts
|
|
486
|
+
var MailteaError = class _MailteaError extends Error {
|
|
487
|
+
status;
|
|
488
|
+
code;
|
|
489
|
+
details;
|
|
490
|
+
requestId;
|
|
491
|
+
constructor(message, init) {
|
|
492
|
+
super(message);
|
|
493
|
+
this.name = "MailteaError";
|
|
494
|
+
this.status = init.status;
|
|
495
|
+
this.code = init.code;
|
|
496
|
+
this.details = init.details;
|
|
497
|
+
this.requestId = init.requestId;
|
|
498
|
+
Object.setPrototypeOf(this, _MailteaError.prototype);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/client.ts
|
|
503
|
+
var DEFAULT_BASE_URL = "https://api.mailtea.app";
|
|
504
|
+
function readEnvApiKey() {
|
|
505
|
+
if (typeof process !== "undefined" && process.env) {
|
|
506
|
+
return process.env.MAILTEA_API_KEY;
|
|
507
|
+
}
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
var Mailtea = class {
|
|
511
|
+
/** The `emails` resource: send, batch, get, update, reschedule, cancel. */
|
|
512
|
+
emails;
|
|
513
|
+
/** The `contacts` resource: create, list, get, update, delete. */
|
|
514
|
+
contacts;
|
|
515
|
+
/** The `segments` resource: create, list, get, update, delete. */
|
|
516
|
+
segments;
|
|
517
|
+
/** The `tags` resource: create, list, get, update, delete. */
|
|
518
|
+
tags;
|
|
519
|
+
/** The `posts` resource: sendTest (newsletter posts). */
|
|
520
|
+
posts;
|
|
521
|
+
/** The `domains` resource: create, list, get, verify, update, delete. */
|
|
522
|
+
domains;
|
|
523
|
+
/** The `webhooks` resource: create, list, get, update, delete. */
|
|
524
|
+
webhooks;
|
|
525
|
+
/** The `contactProperties` resource: create, list, update, delete. */
|
|
526
|
+
contactProperties;
|
|
527
|
+
/** The `apiKeys` resource: create, list, revoke. */
|
|
528
|
+
apiKeys;
|
|
529
|
+
apiKey;
|
|
530
|
+
baseUrl;
|
|
531
|
+
fetchImpl;
|
|
532
|
+
constructor(apiKeyOrOptions, maybeOptions) {
|
|
533
|
+
const options = typeof apiKeyOrOptions === "string" ? { ...maybeOptions, apiKey: apiKeyOrOptions } : apiKeyOrOptions ?? maybeOptions ?? {};
|
|
534
|
+
const apiKey = options.apiKey ?? readEnvApiKey();
|
|
535
|
+
if (!apiKey) {
|
|
536
|
+
throw new MailteaError(
|
|
537
|
+
"Missing Mailtea API key. Pass it to `new Mailtea(apiKey)` or set the MAILTEA_API_KEY environment variable.",
|
|
538
|
+
{ status: 0, code: "missing_api_key" }
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch;
|
|
542
|
+
if (typeof resolvedFetch !== "function") {
|
|
543
|
+
throw new MailteaError(
|
|
544
|
+
"No global `fetch` is available. Use Node.js 18+ (or Bun/Deno), or pass a `fetch` implementation in the Mailtea options.",
|
|
545
|
+
{ status: 0, code: "missing_fetch" }
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
this.apiKey = apiKey;
|
|
549
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
550
|
+
this.fetchImpl = resolvedFetch;
|
|
551
|
+
const request = this.request.bind(this);
|
|
552
|
+
this.emails = new Emails(request);
|
|
553
|
+
this.contacts = new Contacts(request);
|
|
554
|
+
this.segments = new Segments(request);
|
|
555
|
+
this.tags = new Tags(request);
|
|
556
|
+
this.posts = new Posts(request);
|
|
557
|
+
this.domains = new Domains(request);
|
|
558
|
+
this.webhooks = new Webhooks(request);
|
|
559
|
+
this.contactProperties = new ContactProperties(request);
|
|
560
|
+
this.apiKeys = new ApiKeys(request);
|
|
561
|
+
}
|
|
562
|
+
/** @internal Issue an authenticated request and map errors to MailteaError. */
|
|
563
|
+
async request(method, path, body) {
|
|
564
|
+
const headers = {
|
|
565
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
566
|
+
};
|
|
567
|
+
if (body !== void 0) {
|
|
568
|
+
headers["Content-Type"] = "application/json";
|
|
569
|
+
}
|
|
570
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
571
|
+
method,
|
|
572
|
+
headers,
|
|
573
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
574
|
+
});
|
|
575
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
576
|
+
if (!response.ok) {
|
|
577
|
+
let message = `${response.status} ${response.statusText}`.trim();
|
|
578
|
+
let details;
|
|
579
|
+
try {
|
|
580
|
+
const errorBody = await response.json();
|
|
581
|
+
if (errorBody?.error) message = errorBody.error;
|
|
582
|
+
details = errorBody?.details;
|
|
583
|
+
} catch {
|
|
584
|
+
}
|
|
585
|
+
throw new MailteaError(message, {
|
|
586
|
+
status: response.status,
|
|
587
|
+
details,
|
|
588
|
+
requestId
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (response.status === 204) {
|
|
592
|
+
return void 0;
|
|
593
|
+
}
|
|
594
|
+
const text = await response.text();
|
|
595
|
+
return text ? JSON.parse(text) : void 0;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/webhook-signing.ts
|
|
600
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
601
|
+
var SECRET_PREFIX = "whsec_";
|
|
602
|
+
var SIGNATURE_VERSION = "v1";
|
|
603
|
+
function decodeSigningKey(secret) {
|
|
604
|
+
const raw = secret.startsWith(SECRET_PREFIX) ? secret.slice(SECRET_PREFIX.length) : secret;
|
|
605
|
+
return Buffer.from(raw, "base64");
|
|
606
|
+
}
|
|
607
|
+
function computeSignature(input) {
|
|
608
|
+
const timestampSeconds = Math.floor(input.timestamp);
|
|
609
|
+
const signedContent = `${input.msgId}.${timestampSeconds}.${input.payload}`;
|
|
610
|
+
const key = decodeSigningKey(input.secret);
|
|
611
|
+
const digest = createHmac("sha256", key).update(signedContent, "utf8").digest("base64");
|
|
612
|
+
return digest;
|
|
613
|
+
}
|
|
614
|
+
function signWebhook(input) {
|
|
615
|
+
return `${SIGNATURE_VERSION},${computeSignature(input)}`;
|
|
616
|
+
}
|
|
617
|
+
function safeEqual(a, b) {
|
|
618
|
+
const bufA = Buffer.from(a, "utf8");
|
|
619
|
+
const bufB = Buffer.from(b, "utf8");
|
|
620
|
+
if (bufA.length !== bufB.length) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
return timingSafeEqual(bufA, bufB);
|
|
624
|
+
}
|
|
625
|
+
function verifyWebhookSignature(input) {
|
|
626
|
+
const tolerance = input.toleranceSeconds ?? 300;
|
|
627
|
+
const nowSeconds = input.now ?? Math.floor(Date.now() / 1e3);
|
|
628
|
+
const timestampSeconds = Math.floor(input.timestamp);
|
|
629
|
+
if (!Number.isFinite(timestampSeconds)) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
if (Math.abs(nowSeconds - timestampSeconds) > tolerance) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const expected = computeSignature({
|
|
636
|
+
secret: input.secret,
|
|
637
|
+
msgId: input.msgId,
|
|
638
|
+
timestamp: timestampSeconds,
|
|
639
|
+
payload: input.payload
|
|
640
|
+
});
|
|
641
|
+
const tokens = input.signatureHeader.split(" ").filter(Boolean);
|
|
642
|
+
for (const token of tokens) {
|
|
643
|
+
const commaIndex = token.indexOf(",");
|
|
644
|
+
if (commaIndex === -1) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
const version = token.slice(0, commaIndex);
|
|
648
|
+
const signature = token.slice(commaIndex + 1);
|
|
649
|
+
if (version === SIGNATURE_VERSION && safeEqual(signature, expected)) {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
export {
|
|
656
|
+
ApiKeys,
|
|
657
|
+
ContactProperties,
|
|
658
|
+
Contacts,
|
|
659
|
+
Domains,
|
|
660
|
+
Emails,
|
|
661
|
+
InboundAttachments,
|
|
662
|
+
InboundEmails,
|
|
663
|
+
Mailtea,
|
|
664
|
+
MailteaError,
|
|
665
|
+
Posts,
|
|
666
|
+
Segments,
|
|
667
|
+
Tags,
|
|
668
|
+
Webhooks,
|
|
669
|
+
signWebhook,
|
|
670
|
+
verifyWebhookSignature
|
|
671
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mailtea-sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Mailtea Node.js SDK — send, schedule, and manage email, contacts, and segments from your app or AI agent.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mailtea",
|
|
21
|
+
"email",
|
|
22
|
+
"newsletter",
|
|
23
|
+
"transactional-email",
|
|
24
|
+
"sdk",
|
|
25
|
+
"ai-agent"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/mailtea-app/mailtea-node.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://mailtea.app",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"tsup": "^8.4.0",
|
|
37
|
+
"tsx": "^4.20.5",
|
|
38
|
+
"typescript": "^5.8.2",
|
|
39
|
+
"@mailtea/contracts": "0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "tsup src/index.ts --watch --format esm --dts",
|
|
43
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"lint": "echo \"No lint for mailtea yet\"",
|
|
46
|
+
"test": "tsx --test src/*.test.ts"
|
|
47
|
+
}
|
|
48
|
+
}
|