retransmit.dev 0.3.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # retransmit.dev
2
2
 
3
+ [![CI](https://github.com/retransmit-dev/retransmit-node/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/retransmit-dev/retransmit-node/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/retransmit.dev)](https://www.npmjs.com/package/retransmit.dev)
5
+ [![license](https://img.shields.io/npm/l/retransmit.dev)](https://github.com/retransmit-dev/retransmit-node/blob/main/LICENSE)
6
+
3
7
  Node.js SDK for the [Retransmit](https://retransmit.dev) messaging API. Send email, SMS, and WhatsApp through one typed client. Zero dependencies, works on Node 18+ and edge runtimes with `fetch`.
4
8
 
5
9
  | Channel | SDK namespace | Availability |
@@ -74,6 +78,103 @@ Names and values allow letters, digits, underscores and dashes, up to 256
74
78
  characters each. Names must be unique within one email. Batch emails accept
75
79
  the same `tags` field.
76
80
 
81
+ ### Custom headers
82
+
83
+ Add your own message headers with `headers`. The common case is a unique
84
+ `X-Entity-Ref-ID` per email so Gmail does not thread related messages, such as
85
+ order updates with the same subject, into one conversation.
86
+
87
+ ```ts
88
+ await retransmit.emails.send({
89
+ from: "Acme <hello@yourdomain.com>",
90
+ to: "user@example.com",
91
+ subject: "Your order shipped",
92
+ html: "<p>Order #4821 is on its way.</p>",
93
+ headers: { "X-Entity-Ref-ID": "order_4821" },
94
+ });
95
+ ```
96
+
97
+ Up to 20 headers per email. Names are printable ASCII without `:` and may
98
+ only appear once; values are a single line. Headers Retransmit sets itself,
99
+ like `From`, `To`, `Subject`, `Date` or `Message-ID`, are rejected with a
100
+ `validation_error`. Marketing emails keep the hosted `List-Unsubscribe`
101
+ headers even if you pass your own. Headers come back on `emails.get`, and
102
+ batch emails accept the same field.
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
+
77
178
  ### List and filter emails
78
179
 
79
180
  `emails.list` returns your emails newest first. Every tag you pass must match.
@@ -133,10 +234,13 @@ console.log(data?.status); // "sent" | "delivered" | "undelivered" | ...
133
234
  Send up to 10,000 emails in one request:
134
235
 
135
236
  ```ts
136
- const { data: batch } = await retransmit.batch.send([
137
- { from: "Acme <hello@yourdomain.com>", to: "a@example.com", subject: "Hi", text: "Hello A" },
138
- { from: "Acme <hello@yourdomain.com>", to: "b@example.com", subject: "Hi", text: "Hello B" },
139
- ]);
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
+ );
140
244
 
141
245
  const { data: progress } = await retransmit.batch.get(batch!.id);
142
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,
@@ -43,7 +61,9 @@ function toWirePayload(options) {
43
61
  html: options.html,
44
62
  text: options.text,
45
63
  marketing: options.marketing,
46
- tags: options.tags
64
+ tags: options.tags,
65
+ headers: options.headers,
66
+ attachments: options.attachments?.map(toWireAttachment)
47
67
  };
48
68
  }
49
69
  var Emails = class {
@@ -51,9 +71,18 @@ var Emails = class {
51
71
  this.client = client;
52
72
  }
53
73
  client;
54
- /** Queues a single email. Poll `get(id)` or subscribe to webhooks for the outcome. */
55
- send(options) {
56
- return this.client.request("POST", "/v1/emails", toWirePayload(options));
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
+ );
57
86
  }
58
87
  /** Retrieves an email with its current status and event history. */
59
88
  get(id) {
@@ -76,6 +105,21 @@ var Emails = class {
76
105
  tags() {
77
106
  return this.client.request("GET", "/v1/emails/tags");
78
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
+ }
79
123
  };
80
124
 
81
125
  // src/batch.ts
@@ -84,11 +128,19 @@ var Batch = class {
84
128
  this.client = client;
85
129
  }
86
130
  client;
87
- /** Queues up to 10,000 emails in one request. Track progress with `get(id)`. */
88
- send(emails) {
89
- return this.client.request("POST", "/v1/emails/batch", {
90
- emails: emails.map(toWirePayload)
91
- });
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
+ );
92
144
  }
93
145
  /** Batch progress: how many emails are in each status so far. */
94
146
  get(id) {
@@ -147,7 +199,7 @@ var Whatsapp = class {
147
199
 
148
200
  // src/retransmit.ts
149
201
  var DEFAULT_BASE_URL = "https://api.retransmit.dev";
150
- var USER_AGENT = "retransmit.dev-node/0.3.0";
202
+ var USER_AGENT = "retransmit.dev-node/0.5.0";
151
203
  function readEnv(name) {
152
204
  return typeof process !== "undefined" ? process.env?.[name] : void 0;
153
205
  }
@@ -172,7 +224,7 @@ var Retransmit = class {
172
224
  );
173
225
  }
174
226
  /** Internal transport shared by the resource classes. API failures are returned, never thrown. */
175
- async request(method, path, body, query) {
227
+ async request(method, path, body, query, options = {}) {
176
228
  const params = new URLSearchParams();
177
229
  for (const [key, value] of Object.entries(query ?? {})) {
178
230
  if (value === void 0) continue;
@@ -180,15 +232,19 @@ var Retransmit = class {
180
232
  }
181
233
  const encoded = params.toString();
182
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
+ }
183
243
  let response;
184
244
  try {
185
245
  response = await fetch(`${this.baseUrl}${path}${search}`, {
186
246
  method,
187
- headers: {
188
- Authorization: `Bearer ${this.apiKey}`,
189
- "Content-Type": "application/json",
190
- "User-Agent": USER_AGENT
191
- },
247
+ headers,
192
248
  body: body === void 0 ? void 0 : JSON.stringify(body)
193
249
  });
194
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;
@@ -47,6 +59,68 @@ interface SendEmailOptions {
47
59
  * and are never sent to the recipient.
48
60
  */
49
61
  tags?: EmailTag[];
62
+ /**
63
+ * Custom message headers, keyed by header name, for example
64
+ * `{ "X-Entity-Ref-ID": "order_4821" }` to stop Gmail threading related
65
+ * emails together. Up to 20 headers. Names are printable ASCII without `:`
66
+ * (up to 126 characters); values are single-line, up to 870 characters.
67
+ * Headers Retransmit sets itself (From, To, Cc, Bcc, Reply-To, Subject,
68
+ * Date, Message-ID, Return-Path, MIME/Content-* and DKIM-Signature) are
69
+ * rejected with a `validation_error`. On marketing emails the hosted
70
+ * List-Unsubscribe headers take precedence over yours.
71
+ */
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[];
80
+ }
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[];
50
124
  }
51
125
  interface EmailTag {
52
126
  name: string;
@@ -72,11 +146,15 @@ interface GetEmailResponse {
72
146
  subject: string;
73
147
  marketing: boolean;
74
148
  tags: EmailTag[];
149
+ /** Custom headers given at send time, or null. */
150
+ headers: EmailHeaders | null;
75
151
  status: EmailStatus;
76
152
  error: string | null;
77
153
  created_at: string;
78
154
  last_event_at: string | null;
79
155
  events: EmailEvent[];
156
+ /** Attachments given at send time, without download links. See `emails.attachments`. */
157
+ attachments: EmailAttachment[];
80
158
  }
81
159
  interface ListEmailsOptions {
82
160
  /**
@@ -255,8 +333,12 @@ interface GetBatchResponse {
255
333
  declare class Batch {
256
334
  private readonly client;
257
335
  constructor(client: Retransmit);
258
- /** Queues up to 10,000 emails in one request. Track progress with `get(id)`. */
259
- send(emails: SendEmailOptions[]): Promise<Result<SendBatchResponse>>;
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>>;
260
342
  /** Batch progress: how many emails are in each status so far. */
261
343
  get(id: string): Promise<Result<GetBatchResponse>>;
262
344
  }
@@ -264,8 +346,11 @@ declare class Batch {
264
346
  declare class Emails {
265
347
  private readonly client;
266
348
  constructor(client: Retransmit);
267
- /** Queues a single email. Poll `get(id)` or subscribe to webhooks for the outcome. */
268
- send(options: SendEmailOptions): Promise<Result<SendEmailResponse>>;
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>>;
269
354
  /** Retrieves an email with its current status and event history. */
270
355
  get(id: string): Promise<Result<GetEmailResponse>>;
271
356
  /**
@@ -275,6 +360,14 @@ declare class Emails {
275
360
  list(options?: ListEmailsOptions): Promise<Result<ListEmailsResponse>>;
276
361
  /** Every distinct tag on your emails, with a count of emails carrying it. */
277
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>>;
278
371
  }
279
372
 
280
373
  declare class Sms {
@@ -308,7 +401,7 @@ declare class Retransmit {
308
401
  private readonly baseUrl;
309
402
  constructor(apiKey?: string, options?: RetransmitOptions);
310
403
  /** Internal transport shared by the resource classes. API failures are returned, never thrown. */
311
- request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>): Promise<Result<T>>;
404
+ request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>, options?: RequestOptions): Promise<Result<T>>;
312
405
  }
313
406
 
314
- 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;
@@ -47,6 +59,68 @@ interface SendEmailOptions {
47
59
  * and are never sent to the recipient.
48
60
  */
49
61
  tags?: EmailTag[];
62
+ /**
63
+ * Custom message headers, keyed by header name, for example
64
+ * `{ "X-Entity-Ref-ID": "order_4821" }` to stop Gmail threading related
65
+ * emails together. Up to 20 headers. Names are printable ASCII without `:`
66
+ * (up to 126 characters); values are single-line, up to 870 characters.
67
+ * Headers Retransmit sets itself (From, To, Cc, Bcc, Reply-To, Subject,
68
+ * Date, Message-ID, Return-Path, MIME/Content-* and DKIM-Signature) are
69
+ * rejected with a `validation_error`. On marketing emails the hosted
70
+ * List-Unsubscribe headers take precedence over yours.
71
+ */
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[];
80
+ }
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[];
50
124
  }
51
125
  interface EmailTag {
52
126
  name: string;
@@ -72,11 +146,15 @@ interface GetEmailResponse {
72
146
  subject: string;
73
147
  marketing: boolean;
74
148
  tags: EmailTag[];
149
+ /** Custom headers given at send time, or null. */
150
+ headers: EmailHeaders | null;
75
151
  status: EmailStatus;
76
152
  error: string | null;
77
153
  created_at: string;
78
154
  last_event_at: string | null;
79
155
  events: EmailEvent[];
156
+ /** Attachments given at send time, without download links. See `emails.attachments`. */
157
+ attachments: EmailAttachment[];
80
158
  }
81
159
  interface ListEmailsOptions {
82
160
  /**
@@ -255,8 +333,12 @@ interface GetBatchResponse {
255
333
  declare class Batch {
256
334
  private readonly client;
257
335
  constructor(client: Retransmit);
258
- /** Queues up to 10,000 emails in one request. Track progress with `get(id)`. */
259
- send(emails: SendEmailOptions[]): Promise<Result<SendBatchResponse>>;
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>>;
260
342
  /** Batch progress: how many emails are in each status so far. */
261
343
  get(id: string): Promise<Result<GetBatchResponse>>;
262
344
  }
@@ -264,8 +346,11 @@ declare class Batch {
264
346
  declare class Emails {
265
347
  private readonly client;
266
348
  constructor(client: Retransmit);
267
- /** Queues a single email. Poll `get(id)` or subscribe to webhooks for the outcome. */
268
- send(options: SendEmailOptions): Promise<Result<SendEmailResponse>>;
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>>;
269
354
  /** Retrieves an email with its current status and event history. */
270
355
  get(id: string): Promise<Result<GetEmailResponse>>;
271
356
  /**
@@ -275,6 +360,14 @@ declare class Emails {
275
360
  list(options?: ListEmailsOptions): Promise<Result<ListEmailsResponse>>;
276
361
  /** Every distinct tag on your emails, with a count of emails carrying it. */
277
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>>;
278
371
  }
279
372
 
280
373
  declare class Sms {
@@ -308,7 +401,7 @@ declare class Retransmit {
308
401
  private readonly baseUrl;
309
402
  constructor(apiKey?: string, options?: RetransmitOptions);
310
403
  /** Internal transport shared by the resource classes. API failures are returned, never thrown. */
311
- request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>): Promise<Result<T>>;
404
+ request<T>(method: "GET" | "POST", path: string, body?: unknown, query?: Record<string, string | number | string[] | undefined>, options?: RequestOptions): Promise<Result<T>>;
312
405
  }
313
406
 
314
- 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 };