retransmit.dev 0.4.0 → 0.5.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/README.md +81 -4
- package/dist/index.cjs +71 -16
- package/dist/index.d.cts +85 -6
- package/dist/index.d.ts +85 -6
- package/dist/index.js +71 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,80 @@ like `From`, `To`, `Subject`, `Date` or `Message-ID`, are rejected with a
|
|
|
101
101
|
headers even if you pass your own. Headers come back on `emails.get`, and
|
|
102
102
|
batch emails accept the same field.
|
|
103
103
|
|
|
104
|
+
### Attachments
|
|
105
|
+
|
|
106
|
+
Attach up to 20 files, 30 MB in total. Pass the bytes as `content` (a
|
|
107
|
+
`Buffer`, `Uint8Array` or base64 string), or a public URL as `path` and
|
|
108
|
+
Retransmit fetches it while the request runs. Either way the file travels
|
|
109
|
+
inside the email.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { readFile } from "node:fs/promises";
|
|
113
|
+
|
|
114
|
+
await retransmit.emails.send({
|
|
115
|
+
from: "Acme <billing@yourdomain.com>",
|
|
116
|
+
to: "user@example.com",
|
|
117
|
+
subject: "Your invoice",
|
|
118
|
+
html: "<p>Your invoice is attached.</p>",
|
|
119
|
+
attachments: [
|
|
120
|
+
{ filename: "invoice.pdf", content: await readFile("./invoice.pdf") },
|
|
121
|
+
{ filename: "terms.pdf", path: "https://yourdomain.com/terms.pdf" },
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
To embed an image in the HTML, give it a `contentId` and reference it as
|
|
127
|
+
`cid:`:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
await retransmit.emails.send({
|
|
131
|
+
from: "Acme <hello@yourdomain.com>",
|
|
132
|
+
to: "user@example.com",
|
|
133
|
+
subject: "Welcome",
|
|
134
|
+
html: '<p><img src="cid:logo" alt="Acme" /> Glad to have you.</p>',
|
|
135
|
+
attachments: [{ filename: "logo.png", path: "https://yourdomain.com/logo.png", contentId: "logo" }],
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Executables, scripts and installers are rejected with `invalid_attachment`,
|
|
140
|
+
and `batch.send` does not accept attachments. Files are kept for 30 days so
|
|
141
|
+
you can see what went out:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const { data } = await retransmit.emails.attachments("em_xxxxxxxxxxxx");
|
|
145
|
+
// data.attachments: [{ id, filename, content_type, size, download_url, expires_at, ... }]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`download_url` is signed and valid for one hour; it is `null` once the file
|
|
149
|
+
has expired.
|
|
150
|
+
|
|
151
|
+
### Idempotency
|
|
152
|
+
|
|
153
|
+
A send can succeed on the server and still fail on your side, through a
|
|
154
|
+
timeout or a dropped connection. Retrying it blindly sends the email twice.
|
|
155
|
+
Pass an `idempotencyKey` and retries become safe: for 24 hours the same key
|
|
156
|
+
with the same payload returns the original response, `id` included, and
|
|
157
|
+
nothing is queued again.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
await retransmit.emails.send(
|
|
161
|
+
{
|
|
162
|
+
from: "Acme <hello@yourdomain.com>",
|
|
163
|
+
to: "user@example.com",
|
|
164
|
+
subject: "Welcome to Acme",
|
|
165
|
+
html: "<p>Glad to have you.</p>",
|
|
166
|
+
},
|
|
167
|
+
{ idempotencyKey: "welcome-user/123" },
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Use a value that identifies that exact email, such as a UUID or
|
|
172
|
+
`<event>/<entity-id>`. Keys are 1 to 256 characters and are shared by every
|
|
173
|
+
API key in your organization. The same key with a different payload returns
|
|
174
|
+
`invalid_idempotent_request`; a retry that overlaps the first request returns
|
|
175
|
+
`concurrent_idempotent_requests`, so wait a moment and try again. Batches
|
|
176
|
+
accept the same option with one key for the whole batch.
|
|
177
|
+
|
|
104
178
|
### List and filter emails
|
|
105
179
|
|
|
106
180
|
`emails.list` returns your emails newest first. Every tag you pass must match.
|
|
@@ -160,10 +234,13 @@ console.log(data?.status); // "sent" | "delivered" | "undelivered" | ...
|
|
|
160
234
|
Send up to 10,000 emails in one request:
|
|
161
235
|
|
|
162
236
|
```ts
|
|
163
|
-
const { data: batch } = await retransmit.batch.send(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
237
|
+
const { data: batch } = await retransmit.batch.send(
|
|
238
|
+
[
|
|
239
|
+
{ from: "Acme <hello@yourdomain.com>", to: "a@example.com", subject: "Hi", text: "Hello A" },
|
|
240
|
+
{ from: "Acme <hello@yourdomain.com>", to: "b@example.com", subject: "Hi", text: "Hello B" },
|
|
241
|
+
],
|
|
242
|
+
{ idempotencyKey: "weekly-digest/2026-09-07" },
|
|
243
|
+
);
|
|
167
244
|
|
|
168
245
|
const { data: progress } = await retransmit.batch.get(batch!.id);
|
|
169
246
|
console.log(progress?.processed, "/", progress?.total, progress?.counts);
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,24 @@ __export(index_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
33
|
|
|
34
34
|
// src/emails.ts
|
|
35
|
+
function toBase64(bytes) {
|
|
36
|
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
37
|
+
let binary = "";
|
|
38
|
+
const CHUNK = 32768;
|
|
39
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
40
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
41
|
+
}
|
|
42
|
+
return btoa(binary);
|
|
43
|
+
}
|
|
44
|
+
function toWireAttachment(attachment) {
|
|
45
|
+
return {
|
|
46
|
+
filename: attachment.filename,
|
|
47
|
+
content: attachment.content === void 0 || typeof attachment.content === "string" ? attachment.content : toBase64(attachment.content),
|
|
48
|
+
path: attachment.path,
|
|
49
|
+
content_type: attachment.contentType,
|
|
50
|
+
content_id: attachment.contentId
|
|
51
|
+
};
|
|
52
|
+
}
|
|
35
53
|
function toWirePayload(options) {
|
|
36
54
|
return {
|
|
37
55
|
from: options.from,
|
|
@@ -44,7 +62,8 @@ function toWirePayload(options) {
|
|
|
44
62
|
text: options.text,
|
|
45
63
|
marketing: options.marketing,
|
|
46
64
|
tags: options.tags,
|
|
47
|
-
headers: options.headers
|
|
65
|
+
headers: options.headers,
|
|
66
|
+
attachments: options.attachments?.map(toWireAttachment)
|
|
48
67
|
};
|
|
49
68
|
}
|
|
50
69
|
var Emails = class {
|
|
@@ -52,9 +71,18 @@ var Emails = class {
|
|
|
52
71
|
this.client = client;
|
|
53
72
|
}
|
|
54
73
|
client;
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Queues a single email. Poll `get(id)` or subscribe to webhooks for the
|
|
76
|
+
* outcome. Pass `{ idempotencyKey }` to make the call safe to retry.
|
|
77
|
+
*/
|
|
78
|
+
send(options, requestOptions) {
|
|
79
|
+
return this.client.request(
|
|
80
|
+
"POST",
|
|
81
|
+
"/v1/emails",
|
|
82
|
+
toWirePayload(options),
|
|
83
|
+
void 0,
|
|
84
|
+
requestOptions
|
|
85
|
+
);
|
|
58
86
|
}
|
|
59
87
|
/** Retrieves an email with its current status and event history. */
|
|
60
88
|
get(id) {
|
|
@@ -77,6 +105,21 @@ var Emails = class {
|
|
|
77
105
|
tags() {
|
|
78
106
|
return this.client.request("GET", "/v1/emails/tags");
|
|
79
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* The attachments of an email, each with a signed `download_url` valid for
|
|
110
|
+
* one hour. Files are kept for 30 days after the send; after that the link
|
|
111
|
+
* is `null` and only the metadata remains.
|
|
112
|
+
*/
|
|
113
|
+
attachments(emailId) {
|
|
114
|
+
return this.client.request("GET", `/v1/emails/${encodeURIComponent(emailId)}/attachments`);
|
|
115
|
+
}
|
|
116
|
+
/** One attachment of an email with a signed download link. */
|
|
117
|
+
getAttachment(emailId, attachmentId) {
|
|
118
|
+
return this.client.request(
|
|
119
|
+
"GET",
|
|
120
|
+
`/v1/emails/${encodeURIComponent(emailId)}/attachments/${encodeURIComponent(attachmentId)}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
80
123
|
};
|
|
81
124
|
|
|
82
125
|
// src/batch.ts
|
|
@@ -85,11 +128,19 @@ var Batch = class {
|
|
|
85
128
|
this.client = client;
|
|
86
129
|
}
|
|
87
130
|
client;
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Queues up to 10,000 emails in one request. Track progress with `get(id)`.
|
|
133
|
+
* Pass `{ idempotencyKey }` covering the whole batch to make the call safe
|
|
134
|
+
* to retry.
|
|
135
|
+
*/
|
|
136
|
+
send(emails, requestOptions) {
|
|
137
|
+
return this.client.request(
|
|
138
|
+
"POST",
|
|
139
|
+
"/v1/emails/batch",
|
|
140
|
+
{ emails: emails.map(toWirePayload) },
|
|
141
|
+
void 0,
|
|
142
|
+
requestOptions
|
|
143
|
+
);
|
|
93
144
|
}
|
|
94
145
|
/** Batch progress: how many emails are in each status so far. */
|
|
95
146
|
get(id) {
|
|
@@ -148,7 +199,7 @@ var Whatsapp = class {
|
|
|
148
199
|
|
|
149
200
|
// src/retransmit.ts
|
|
150
201
|
var DEFAULT_BASE_URL = "https://api.retransmit.dev";
|
|
151
|
-
var USER_AGENT = "retransmit.dev-node/0.
|
|
202
|
+
var USER_AGENT = "retransmit.dev-node/0.5.0";
|
|
152
203
|
function readEnv(name) {
|
|
153
204
|
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
154
205
|
}
|
|
@@ -173,7 +224,7 @@ var Retransmit = class {
|
|
|
173
224
|
);
|
|
174
225
|
}
|
|
175
226
|
/** Internal transport shared by the resource classes. API failures are returned, never thrown. */
|
|
176
|
-
async request(method, path, body, query) {
|
|
227
|
+
async request(method, path, body, query, options = {}) {
|
|
177
228
|
const params = new URLSearchParams();
|
|
178
229
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
179
230
|
if (value === void 0) continue;
|
|
@@ -181,15 +232,19 @@ var Retransmit = class {
|
|
|
181
232
|
}
|
|
182
233
|
const encoded = params.toString();
|
|
183
234
|
const search = encoded ? `?${encoded}` : "";
|
|
235
|
+
const headers = {
|
|
236
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
237
|
+
"Content-Type": "application/json",
|
|
238
|
+
"User-Agent": USER_AGENT
|
|
239
|
+
};
|
|
240
|
+
if (options.idempotencyKey !== void 0) {
|
|
241
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
242
|
+
}
|
|
184
243
|
let response;
|
|
185
244
|
try {
|
|
186
245
|
response = await fetch(`${this.baseUrl}${path}${search}`, {
|
|
187
246
|
method,
|
|
188
|
-
headers
|
|
189
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
"User-Agent": USER_AGENT
|
|
192
|
-
},
|
|
247
|
+
headers,
|
|
193
248
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
194
249
|
});
|
|
195
250
|
} catch (cause) {
|
package/dist/index.d.cts
CHANGED
|
@@ -20,6 +20,18 @@ interface RetransmitOptions {
|
|
|
20
20
|
/** Override the API origin. Defaults to `https://api.retransmit.dev` (or `RETRANSMIT_BASE_URL`). */
|
|
21
21
|
baseUrl?: string;
|
|
22
22
|
}
|
|
23
|
+
/** Per-request options, passed as the second argument of `emails.send` and `batch.send`. */
|
|
24
|
+
interface RequestOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Makes the request safe to retry. Sent as the `Idempotency-Key` header.
|
|
27
|
+
* For 24 hours, a retry with the same key and payload returns the original
|
|
28
|
+
* response instead of queuing the email again. 1 to 256 characters; a UUID
|
|
29
|
+
* or `<event>/<entity-id>` such as `welcome-user/123` works well. A retry
|
|
30
|
+
* with a different payload fails with `invalid_idempotent_request`, and one
|
|
31
|
+
* that overlaps the first request fails with `concurrent_idempotent_requests`.
|
|
32
|
+
*/
|
|
33
|
+
idempotencyKey?: string;
|
|
34
|
+
}
|
|
23
35
|
interface SendEmailOptions {
|
|
24
36
|
/** Sender, as `address@domain.com` or `Name <address@domain.com>`. The domain must be verified on your account. */
|
|
25
37
|
from: string;
|
|
@@ -58,8 +70,58 @@ interface SendEmailOptions {
|
|
|
58
70
|
* List-Unsubscribe headers take precedence over yours.
|
|
59
71
|
*/
|
|
60
72
|
headers?: EmailHeaders;
|
|
73
|
+
/**
|
|
74
|
+
* Files to attach, up to 20 per email and 30 MB in total. Each needs a
|
|
75
|
+
* `filename` and either `content` (the bytes) or `path` (a public URL
|
|
76
|
+
* fetched when you call `send`). Add `contentId` to embed an image inline.
|
|
77
|
+
* Not accepted by `batch.send`.
|
|
78
|
+
*/
|
|
79
|
+
attachments?: Attachment[];
|
|
61
80
|
}
|
|
62
81
|
type EmailHeaders = Record<string, string>;
|
|
82
|
+
/** A file to attach. Provide exactly one of `content` and `path`. */
|
|
83
|
+
interface Attachment {
|
|
84
|
+
/**
|
|
85
|
+
* Name the recipient sees, up to 255 characters, no path separators. Its
|
|
86
|
+
* extension sets the content type when `contentType` is omitted.
|
|
87
|
+
*/
|
|
88
|
+
filename: string;
|
|
89
|
+
/** The file: a `Buffer`, a `Uint8Array`, or an already base64 encoded string. */
|
|
90
|
+
content?: string | Uint8Array;
|
|
91
|
+
/**
|
|
92
|
+
* Public http(s) URL the file is fetched from while the request runs, with
|
|
93
|
+
* a 15 second budget. Private and internal hosts are refused.
|
|
94
|
+
*/
|
|
95
|
+
path?: string;
|
|
96
|
+
/** MIME type such as `application/pdf`. Inferred from the filename when omitted. */
|
|
97
|
+
contentType?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Embeds the file inline and sets its Content-ID. Reference it in `html`
|
|
100
|
+
* as `<img src="cid:the-id">`. Letters, digits, `.`, `_`, `@` and `-`, up
|
|
101
|
+
* to 128 characters, unique per email.
|
|
102
|
+
*/
|
|
103
|
+
contentId?: string;
|
|
104
|
+
}
|
|
105
|
+
/** An attachment as it was sent, without a download link. */
|
|
106
|
+
interface EmailAttachment {
|
|
107
|
+
id: string;
|
|
108
|
+
filename: string;
|
|
109
|
+
content_type: string;
|
|
110
|
+
/** Size in bytes of the decoded file. */
|
|
111
|
+
size: number;
|
|
112
|
+
content_id: string | null;
|
|
113
|
+
/** True for images embedded via `contentId`. */
|
|
114
|
+
inline: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface EmailAttachmentWithDownload extends EmailAttachment {
|
|
117
|
+
/** When the stored file is deleted, 30 days after the send. */
|
|
118
|
+
expires_at: string;
|
|
119
|
+
/** Signed link to download the file, valid for one hour. `null` once the file has expired. */
|
|
120
|
+
download_url: string | null;
|
|
121
|
+
}
|
|
122
|
+
interface ListEmailAttachmentsResponse {
|
|
123
|
+
attachments: EmailAttachmentWithDownload[];
|
|
124
|
+
}
|
|
63
125
|
interface EmailTag {
|
|
64
126
|
name: string;
|
|
65
127
|
value: string;
|
|
@@ -91,6 +153,8 @@ interface GetEmailResponse {
|
|
|
91
153
|
created_at: string;
|
|
92
154
|
last_event_at: string | null;
|
|
93
155
|
events: EmailEvent[];
|
|
156
|
+
/** Attachments given at send time, without download links. See `emails.attachments`. */
|
|
157
|
+
attachments: EmailAttachment[];
|
|
94
158
|
}
|
|
95
159
|
interface ListEmailsOptions {
|
|
96
160
|
/**
|
|
@@ -269,8 +333,12 @@ interface GetBatchResponse {
|
|
|
269
333
|
declare class Batch {
|
|
270
334
|
private readonly client;
|
|
271
335
|
constructor(client: Retransmit);
|
|
272
|
-
/**
|
|
273
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Queues up to 10,000 emails in one request. Track progress with `get(id)`.
|
|
338
|
+
* Pass `{ idempotencyKey }` covering the whole batch to make the call safe
|
|
339
|
+
* to retry.
|
|
340
|
+
*/
|
|
341
|
+
send(emails: SendEmailOptions[], requestOptions?: RequestOptions): Promise<Result<SendBatchResponse>>;
|
|
274
342
|
/** Batch progress: how many emails are in each status so far. */
|
|
275
343
|
get(id: string): Promise<Result<GetBatchResponse>>;
|
|
276
344
|
}
|
|
@@ -278,8 +346,11 @@ declare class Batch {
|
|
|
278
346
|
declare class Emails {
|
|
279
347
|
private readonly client;
|
|
280
348
|
constructor(client: Retransmit);
|
|
281
|
-
/**
|
|
282
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Queues a single email. Poll `get(id)` or subscribe to webhooks for the
|
|
351
|
+
* outcome. Pass `{ idempotencyKey }` to make the call safe to retry.
|
|
352
|
+
*/
|
|
353
|
+
send(options: SendEmailOptions, requestOptions?: RequestOptions): Promise<Result<SendEmailResponse>>;
|
|
283
354
|
/** Retrieves an email with its current status and event history. */
|
|
284
355
|
get(id: string): Promise<Result<GetEmailResponse>>;
|
|
285
356
|
/**
|
|
@@ -289,6 +360,14 @@ declare class Emails {
|
|
|
289
360
|
list(options?: ListEmailsOptions): Promise<Result<ListEmailsResponse>>;
|
|
290
361
|
/** Every distinct tag on your emails, with a count of emails carrying it. */
|
|
291
362
|
tags(): Promise<Result<ListEmailTagsResponse>>;
|
|
363
|
+
/**
|
|
364
|
+
* The attachments of an email, each with a signed `download_url` valid for
|
|
365
|
+
* one hour. Files are kept for 30 days after the send; after that the link
|
|
366
|
+
* is `null` and only the metadata remains.
|
|
367
|
+
*/
|
|
368
|
+
attachments(emailId: string): Promise<Result<ListEmailAttachmentsResponse>>;
|
|
369
|
+
/** One attachment of an email with a signed download link. */
|
|
370
|
+
getAttachment(emailId: string, attachmentId: string): Promise<Result<EmailAttachmentWithDownload>>;
|
|
292
371
|
}
|
|
293
372
|
|
|
294
373
|
declare class Sms {
|
|
@@ -322,7 +401,7 @@ declare class Retransmit {
|
|
|
322
401
|
private readonly baseUrl;
|
|
323
402
|
constructor(apiKey?: string, options?: RetransmitOptions);
|
|
324
403
|
/** Internal transport shared by the resource classes. API failures are returned, never thrown. */
|
|
325
|
-
request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined
|
|
404
|
+
request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>, options?: RequestOptions): Promise<Result<T>>;
|
|
326
405
|
}
|
|
327
406
|
|
|
328
|
-
export { Batch, EMAIL_STATUSES, type EmailEvent, type EmailStatus, type EmailSummary, type EmailTag, type EmailTagCount, Emails, type GetBatchResponse, type GetEmailResponse, type GetSmsResponse, type GetWhatsappResponse, type ListEmailTagsResponse, type ListEmailsOptions, type ListEmailsResponse, type Result, Retransmit, type RetransmitError, type RetransmitOptions, SMS_STATUSES, type SendBatchResponse, type SendEmailOptions, type SendEmailResponse, type SendSmsOptions, type SendSmsResponse, type SendWhatsappOptions, type SendWhatsappResponse, Sms, type SmsEvent, type SmsStatus, WHATSAPP_STATUSES, Whatsapp, type WhatsappDocument, type WhatsappEvent, type WhatsappMedia, type WhatsappMessageType, type WhatsappStatus, type WhatsappTemplate };
|
|
407
|
+
export { type Attachment, Batch, EMAIL_STATUSES, type EmailAttachment, type EmailAttachmentWithDownload, type EmailEvent, type EmailStatus, type EmailSummary, type EmailTag, type EmailTagCount, Emails, type GetBatchResponse, type GetEmailResponse, type GetSmsResponse, type GetWhatsappResponse, type ListEmailAttachmentsResponse, type ListEmailTagsResponse, type ListEmailsOptions, type ListEmailsResponse, type RequestOptions, type Result, Retransmit, type RetransmitError, type RetransmitOptions, SMS_STATUSES, type SendBatchResponse, type SendEmailOptions, type SendEmailResponse, type SendSmsOptions, type SendSmsResponse, type SendWhatsappOptions, type SendWhatsappResponse, Sms, type SmsEvent, type SmsStatus, WHATSAPP_STATUSES, Whatsapp, type WhatsappDocument, type WhatsappEvent, type WhatsappMedia, type WhatsappMessageType, type WhatsappStatus, type WhatsappTemplate };
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,18 @@ interface RetransmitOptions {
|
|
|
20
20
|
/** Override the API origin. Defaults to `https://api.retransmit.dev` (or `RETRANSMIT_BASE_URL`). */
|
|
21
21
|
baseUrl?: string;
|
|
22
22
|
}
|
|
23
|
+
/** Per-request options, passed as the second argument of `emails.send` and `batch.send`. */
|
|
24
|
+
interface RequestOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Makes the request safe to retry. Sent as the `Idempotency-Key` header.
|
|
27
|
+
* For 24 hours, a retry with the same key and payload returns the original
|
|
28
|
+
* response instead of queuing the email again. 1 to 256 characters; a UUID
|
|
29
|
+
* or `<event>/<entity-id>` such as `welcome-user/123` works well. A retry
|
|
30
|
+
* with a different payload fails with `invalid_idempotent_request`, and one
|
|
31
|
+
* that overlaps the first request fails with `concurrent_idempotent_requests`.
|
|
32
|
+
*/
|
|
33
|
+
idempotencyKey?: string;
|
|
34
|
+
}
|
|
23
35
|
interface SendEmailOptions {
|
|
24
36
|
/** Sender, as `address@domain.com` or `Name <address@domain.com>`. The domain must be verified on your account. */
|
|
25
37
|
from: string;
|
|
@@ -58,8 +70,58 @@ interface SendEmailOptions {
|
|
|
58
70
|
* List-Unsubscribe headers take precedence over yours.
|
|
59
71
|
*/
|
|
60
72
|
headers?: EmailHeaders;
|
|
73
|
+
/**
|
|
74
|
+
* Files to attach, up to 20 per email and 30 MB in total. Each needs a
|
|
75
|
+
* `filename` and either `content` (the bytes) or `path` (a public URL
|
|
76
|
+
* fetched when you call `send`). Add `contentId` to embed an image inline.
|
|
77
|
+
* Not accepted by `batch.send`.
|
|
78
|
+
*/
|
|
79
|
+
attachments?: Attachment[];
|
|
61
80
|
}
|
|
62
81
|
type EmailHeaders = Record<string, string>;
|
|
82
|
+
/** A file to attach. Provide exactly one of `content` and `path`. */
|
|
83
|
+
interface Attachment {
|
|
84
|
+
/**
|
|
85
|
+
* Name the recipient sees, up to 255 characters, no path separators. Its
|
|
86
|
+
* extension sets the content type when `contentType` is omitted.
|
|
87
|
+
*/
|
|
88
|
+
filename: string;
|
|
89
|
+
/** The file: a `Buffer`, a `Uint8Array`, or an already base64 encoded string. */
|
|
90
|
+
content?: string | Uint8Array;
|
|
91
|
+
/**
|
|
92
|
+
* Public http(s) URL the file is fetched from while the request runs, with
|
|
93
|
+
* a 15 second budget. Private and internal hosts are refused.
|
|
94
|
+
*/
|
|
95
|
+
path?: string;
|
|
96
|
+
/** MIME type such as `application/pdf`. Inferred from the filename when omitted. */
|
|
97
|
+
contentType?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Embeds the file inline and sets its Content-ID. Reference it in `html`
|
|
100
|
+
* as `<img src="cid:the-id">`. Letters, digits, `.`, `_`, `@` and `-`, up
|
|
101
|
+
* to 128 characters, unique per email.
|
|
102
|
+
*/
|
|
103
|
+
contentId?: string;
|
|
104
|
+
}
|
|
105
|
+
/** An attachment as it was sent, without a download link. */
|
|
106
|
+
interface EmailAttachment {
|
|
107
|
+
id: string;
|
|
108
|
+
filename: string;
|
|
109
|
+
content_type: string;
|
|
110
|
+
/** Size in bytes of the decoded file. */
|
|
111
|
+
size: number;
|
|
112
|
+
content_id: string | null;
|
|
113
|
+
/** True for images embedded via `contentId`. */
|
|
114
|
+
inline: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface EmailAttachmentWithDownload extends EmailAttachment {
|
|
117
|
+
/** When the stored file is deleted, 30 days after the send. */
|
|
118
|
+
expires_at: string;
|
|
119
|
+
/** Signed link to download the file, valid for one hour. `null` once the file has expired. */
|
|
120
|
+
download_url: string | null;
|
|
121
|
+
}
|
|
122
|
+
interface ListEmailAttachmentsResponse {
|
|
123
|
+
attachments: EmailAttachmentWithDownload[];
|
|
124
|
+
}
|
|
63
125
|
interface EmailTag {
|
|
64
126
|
name: string;
|
|
65
127
|
value: string;
|
|
@@ -91,6 +153,8 @@ interface GetEmailResponse {
|
|
|
91
153
|
created_at: string;
|
|
92
154
|
last_event_at: string | null;
|
|
93
155
|
events: EmailEvent[];
|
|
156
|
+
/** Attachments given at send time, without download links. See `emails.attachments`. */
|
|
157
|
+
attachments: EmailAttachment[];
|
|
94
158
|
}
|
|
95
159
|
interface ListEmailsOptions {
|
|
96
160
|
/**
|
|
@@ -269,8 +333,12 @@ interface GetBatchResponse {
|
|
|
269
333
|
declare class Batch {
|
|
270
334
|
private readonly client;
|
|
271
335
|
constructor(client: Retransmit);
|
|
272
|
-
/**
|
|
273
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Queues up to 10,000 emails in one request. Track progress with `get(id)`.
|
|
338
|
+
* Pass `{ idempotencyKey }` covering the whole batch to make the call safe
|
|
339
|
+
* to retry.
|
|
340
|
+
*/
|
|
341
|
+
send(emails: SendEmailOptions[], requestOptions?: RequestOptions): Promise<Result<SendBatchResponse>>;
|
|
274
342
|
/** Batch progress: how many emails are in each status so far. */
|
|
275
343
|
get(id: string): Promise<Result<GetBatchResponse>>;
|
|
276
344
|
}
|
|
@@ -278,8 +346,11 @@ declare class Batch {
|
|
|
278
346
|
declare class Emails {
|
|
279
347
|
private readonly client;
|
|
280
348
|
constructor(client: Retransmit);
|
|
281
|
-
/**
|
|
282
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Queues a single email. Poll `get(id)` or subscribe to webhooks for the
|
|
351
|
+
* outcome. Pass `{ idempotencyKey }` to make the call safe to retry.
|
|
352
|
+
*/
|
|
353
|
+
send(options: SendEmailOptions, requestOptions?: RequestOptions): Promise<Result<SendEmailResponse>>;
|
|
283
354
|
/** Retrieves an email with its current status and event history. */
|
|
284
355
|
get(id: string): Promise<Result<GetEmailResponse>>;
|
|
285
356
|
/**
|
|
@@ -289,6 +360,14 @@ declare class Emails {
|
|
|
289
360
|
list(options?: ListEmailsOptions): Promise<Result<ListEmailsResponse>>;
|
|
290
361
|
/** Every distinct tag on your emails, with a count of emails carrying it. */
|
|
291
362
|
tags(): Promise<Result<ListEmailTagsResponse>>;
|
|
363
|
+
/**
|
|
364
|
+
* The attachments of an email, each with a signed `download_url` valid for
|
|
365
|
+
* one hour. Files are kept for 30 days after the send; after that the link
|
|
366
|
+
* is `null` and only the metadata remains.
|
|
367
|
+
*/
|
|
368
|
+
attachments(emailId: string): Promise<Result<ListEmailAttachmentsResponse>>;
|
|
369
|
+
/** One attachment of an email with a signed download link. */
|
|
370
|
+
getAttachment(emailId: string, attachmentId: string): Promise<Result<EmailAttachmentWithDownload>>;
|
|
292
371
|
}
|
|
293
372
|
|
|
294
373
|
declare class Sms {
|
|
@@ -322,7 +401,7 @@ declare class Retransmit {
|
|
|
322
401
|
private readonly baseUrl;
|
|
323
402
|
constructor(apiKey?: string, options?: RetransmitOptions);
|
|
324
403
|
/** Internal transport shared by the resource classes. API failures are returned, never thrown. */
|
|
325
|
-
request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined
|
|
404
|
+
request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>, options?: RequestOptions): Promise<Result<T>>;
|
|
326
405
|
}
|
|
327
406
|
|
|
328
|
-
export { Batch, EMAIL_STATUSES, type EmailEvent, type EmailStatus, type EmailSummary, type EmailTag, type EmailTagCount, Emails, type GetBatchResponse, type GetEmailResponse, type GetSmsResponse, type GetWhatsappResponse, type ListEmailTagsResponse, type ListEmailsOptions, type ListEmailsResponse, type Result, Retransmit, type RetransmitError, type RetransmitOptions, SMS_STATUSES, type SendBatchResponse, type SendEmailOptions, type SendEmailResponse, type SendSmsOptions, type SendSmsResponse, type SendWhatsappOptions, type SendWhatsappResponse, Sms, type SmsEvent, type SmsStatus, WHATSAPP_STATUSES, Whatsapp, type WhatsappDocument, type WhatsappEvent, type WhatsappMedia, type WhatsappMessageType, type WhatsappStatus, type WhatsappTemplate };
|
|
407
|
+
export { type Attachment, Batch, EMAIL_STATUSES, type EmailAttachment, type EmailAttachmentWithDownload, type EmailEvent, type EmailStatus, type EmailSummary, type EmailTag, type EmailTagCount, Emails, type GetBatchResponse, type GetEmailResponse, type GetSmsResponse, type GetWhatsappResponse, type ListEmailAttachmentsResponse, type ListEmailTagsResponse, type ListEmailsOptions, type ListEmailsResponse, type RequestOptions, type Result, Retransmit, type RetransmitError, type RetransmitOptions, SMS_STATUSES, type SendBatchResponse, type SendEmailOptions, type SendEmailResponse, type SendSmsOptions, type SendSmsResponse, type SendWhatsappOptions, type SendWhatsappResponse, Sms, type SmsEvent, type SmsStatus, WHATSAPP_STATUSES, Whatsapp, type WhatsappDocument, type WhatsappEvent, type WhatsappMedia, type WhatsappMessageType, type WhatsappStatus, type WhatsappTemplate };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
// src/emails.ts
|
|
2
|
+
function toBase64(bytes) {
|
|
3
|
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
4
|
+
let binary = "";
|
|
5
|
+
const CHUNK = 32768;
|
|
6
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
7
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
8
|
+
}
|
|
9
|
+
return btoa(binary);
|
|
10
|
+
}
|
|
11
|
+
function toWireAttachment(attachment) {
|
|
12
|
+
return {
|
|
13
|
+
filename: attachment.filename,
|
|
14
|
+
content: attachment.content === void 0 || typeof attachment.content === "string" ? attachment.content : toBase64(attachment.content),
|
|
15
|
+
path: attachment.path,
|
|
16
|
+
content_type: attachment.contentType,
|
|
17
|
+
content_id: attachment.contentId
|
|
18
|
+
};
|
|
19
|
+
}
|
|
2
20
|
function toWirePayload(options) {
|
|
3
21
|
return {
|
|
4
22
|
from: options.from,
|
|
@@ -11,7 +29,8 @@ function toWirePayload(options) {
|
|
|
11
29
|
text: options.text,
|
|
12
30
|
marketing: options.marketing,
|
|
13
31
|
tags: options.tags,
|
|
14
|
-
headers: options.headers
|
|
32
|
+
headers: options.headers,
|
|
33
|
+
attachments: options.attachments?.map(toWireAttachment)
|
|
15
34
|
};
|
|
16
35
|
}
|
|
17
36
|
var Emails = class {
|
|
@@ -19,9 +38,18 @@ var Emails = class {
|
|
|
19
38
|
this.client = client;
|
|
20
39
|
}
|
|
21
40
|
client;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Queues a single email. Poll `get(id)` or subscribe to webhooks for the
|
|
43
|
+
* outcome. Pass `{ idempotencyKey }` to make the call safe to retry.
|
|
44
|
+
*/
|
|
45
|
+
send(options, requestOptions) {
|
|
46
|
+
return this.client.request(
|
|
47
|
+
"POST",
|
|
48
|
+
"/v1/emails",
|
|
49
|
+
toWirePayload(options),
|
|
50
|
+
void 0,
|
|
51
|
+
requestOptions
|
|
52
|
+
);
|
|
25
53
|
}
|
|
26
54
|
/** Retrieves an email with its current status and event history. */
|
|
27
55
|
get(id) {
|
|
@@ -44,6 +72,21 @@ var Emails = class {
|
|
|
44
72
|
tags() {
|
|
45
73
|
return this.client.request("GET", "/v1/emails/tags");
|
|
46
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The attachments of an email, each with a signed `download_url` valid for
|
|
77
|
+
* one hour. Files are kept for 30 days after the send; after that the link
|
|
78
|
+
* is `null` and only the metadata remains.
|
|
79
|
+
*/
|
|
80
|
+
attachments(emailId) {
|
|
81
|
+
return this.client.request("GET", `/v1/emails/${encodeURIComponent(emailId)}/attachments`);
|
|
82
|
+
}
|
|
83
|
+
/** One attachment of an email with a signed download link. */
|
|
84
|
+
getAttachment(emailId, attachmentId) {
|
|
85
|
+
return this.client.request(
|
|
86
|
+
"GET",
|
|
87
|
+
`/v1/emails/${encodeURIComponent(emailId)}/attachments/${encodeURIComponent(attachmentId)}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
47
90
|
};
|
|
48
91
|
|
|
49
92
|
// src/batch.ts
|
|
@@ -52,11 +95,19 @@ var Batch = class {
|
|
|
52
95
|
this.client = client;
|
|
53
96
|
}
|
|
54
97
|
client;
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Queues up to 10,000 emails in one request. Track progress with `get(id)`.
|
|
100
|
+
* Pass `{ idempotencyKey }` covering the whole batch to make the call safe
|
|
101
|
+
* to retry.
|
|
102
|
+
*/
|
|
103
|
+
send(emails, requestOptions) {
|
|
104
|
+
return this.client.request(
|
|
105
|
+
"POST",
|
|
106
|
+
"/v1/emails/batch",
|
|
107
|
+
{ emails: emails.map(toWirePayload) },
|
|
108
|
+
void 0,
|
|
109
|
+
requestOptions
|
|
110
|
+
);
|
|
60
111
|
}
|
|
61
112
|
/** Batch progress: how many emails are in each status so far. */
|
|
62
113
|
get(id) {
|
|
@@ -115,7 +166,7 @@ var Whatsapp = class {
|
|
|
115
166
|
|
|
116
167
|
// src/retransmit.ts
|
|
117
168
|
var DEFAULT_BASE_URL = "https://api.retransmit.dev";
|
|
118
|
-
var USER_AGENT = "retransmit.dev-node/0.
|
|
169
|
+
var USER_AGENT = "retransmit.dev-node/0.5.0";
|
|
119
170
|
function readEnv(name) {
|
|
120
171
|
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
121
172
|
}
|
|
@@ -140,7 +191,7 @@ var Retransmit = class {
|
|
|
140
191
|
);
|
|
141
192
|
}
|
|
142
193
|
/** Internal transport shared by the resource classes. API failures are returned, never thrown. */
|
|
143
|
-
async request(method, path, body, query) {
|
|
194
|
+
async request(method, path, body, query, options = {}) {
|
|
144
195
|
const params = new URLSearchParams();
|
|
145
196
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
146
197
|
if (value === void 0) continue;
|
|
@@ -148,15 +199,19 @@ var Retransmit = class {
|
|
|
148
199
|
}
|
|
149
200
|
const encoded = params.toString();
|
|
150
201
|
const search = encoded ? `?${encoded}` : "";
|
|
202
|
+
const headers = {
|
|
203
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
"User-Agent": USER_AGENT
|
|
206
|
+
};
|
|
207
|
+
if (options.idempotencyKey !== void 0) {
|
|
208
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
209
|
+
}
|
|
151
210
|
let response;
|
|
152
211
|
try {
|
|
153
212
|
response = await fetch(`${this.baseUrl}${path}${search}`, {
|
|
154
213
|
method,
|
|
155
|
-
headers
|
|
156
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
157
|
-
"Content-Type": "application/json",
|
|
158
|
-
"User-Agent": USER_AGENT
|
|
159
|
-
},
|
|
214
|
+
headers,
|
|
160
215
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
161
216
|
});
|
|
162
217
|
} catch (cause) {
|