@crvouga/mockingbird-service-resend 2.1.0 → 2.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog — @crvouga/mockingbird-service-resend
2
2
 
3
+ ## 2.2.0 (2026-10-05)
4
+
5
+ ### Features
6
+
7
+ - add scripted local API mock ([4a76a11](https://github.com/crvouga/mockingbird/commit/4a76a114e2efad45934e0a93cf29576fd418e15c))
8
+ - add email collection pagination ([c75ffb9](https://github.com/crvouga/mockingbird/commit/c75ffb9db5a04eff4965de8f378b931d56cd8d60))
9
+ - add email collection pagination ([ae926d0](https://github.com/crvouga/mockingbird/commit/ae926d0e91a283e7ad834711bf8e279916f79684))
10
+
11
+ ### Fixes and improvements
12
+
13
+ - support embedded route parameters ([127cad9](https://github.com/crvouga/mockingbird/commit/127cad997ef106950c1b866070eea191ba54403f))
14
+ - preserve namespace URLs after main sync ([62a15ce](https://github.com/crvouga/mockingbird/commit/62a15ce9bd453069bde69e3a30c91efc01f8871b))
15
+
3
16
  ## 2.1.0 (2026-10-04)
4
17
 
5
18
  ### Features
@@ -12,10 +25,6 @@
12
25
  - fill the embedded viewport ([c1a35a4](https://github.com/crvouga/mockingbird/commit/c1a35a4789389ee9cd06c286d09bd47c4d06c71e))
13
26
  - make dialog cleanup immediate ([0c1a3c5](https://github.com/crvouga/mockingbird/commit/0c1a3c5b12584d50c19dcae595b951b8e5417b9c))
14
27
 
15
- ### Dependencies
16
-
17
- - `@crvouga/mockingbird-service-sqlite`
18
-
19
28
  ## 2.0.0 (2026-10-04)
20
29
 
21
30
  ### ⚠️ Breaking changes
package/README.md CHANGED
@@ -77,6 +77,8 @@ await mock.close()
77
77
  | --- | --- |
78
78
  | `POST /emails` | `{from, to, subject, html?, text?, cc?, bcc?, reply_to?, headers?, tags?, attachments?, scheduled_at?}` → `{id}` (a UUID). The SDK renders `react` to HTML before sending, so the mock sees HTML. Violations answer Resend's body `{statusCode, name, message}`: 422 `missing_required_field` (`Missing \`to\` field.`, or no `html`/`text`), 422 `validation_error` for a malformed address (`Invalid \`from\` field. The email address needs to follow …`), a tag outside `[A-Za-z0-9_-]`, or any other contract violation. `Idempotency-Key`: the same key and payload replay the first 200 byte for byte (`idempotent-replayed: true`); another payload is 409 `invalid_idempotent_request`; a key still in flight is 409 `concurrent_idempotent_requests`; a key outside 1–256 characters is 400 `invalid_idempotency_key`. Only 200s are remembered. |
79
79
  | `GET /emails/{id}` | `emails.get`: `{object: "email", id, to, from, created_at, subject, html, text, cc, bcc, reply_to, last_event: "delivered" \| "scheduled", scheduled_at, headers, tags}`. `headers` keeps the names and values from the send; `to` keeps the original address form and order. |
80
+ | `GET /emails` | Newest-first sent email metadata, `{object: "list", has_more, data}`. `limit` defaults to 20 (1–100); `after`/`before` use email IDs and exclude the cursor. Content is available from the detail endpoint. |
81
+ | `GET /emails/receiving` | Newest-first received email metadata with the same envelope and ID cursors. Without `limit`, returns every received email; an explicit limit must be 1–100. Includes attachment metadata and size. |
80
82
  | `GET /emails/receiving/{id}` | A received email: `{object: "email", id, to, from, cc, bcc, reply_to, created_at, subject, html, text, headers, message_id, attachments[{id, filename, content_type, content_disposition, content_id}]}`. |
81
83
  | `GET /emails/receiving/{id}/attachments` | `{object: "list", has_more: false, data: [{id, filename, content_type, content_disposition, content_id, size, download_url, expires_at}]}`. |
82
84
  | `GET /downloads/inbound/{attachment_id}` | The `download_url`: unauthenticated, the bytes with `content-type` and `content-length`. In a non-default namespace the URL carries `/__admin/ns/<name>`. |
package/SUPPORT.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  Generated from `openapi.yaml`; do not edit by hand.
4
4
 
5
- - operations in spec: **5**
6
- - supported by the mock: **5**
7
- - parity enabled: **5**
5
+ - operations in spec: **7**
6
+ - supported by the mock: **7**
7
+ - parity enabled: **7**
8
8
 
9
9
  | operationId | route | mock | parity | notes |
10
10
  | --- | --- | --- | --- | --- |
11
+ | `ListEmails` | `GET /emails` | ✅ supported | ✅ | |
11
12
  | `SendEmail` | `POST /emails` | ✅ supported | ⚠️ unsafe (opt-in) | |
12
13
  | `GetEmail` | `GET /emails/{email_id}` | ✅ supported | ✅ | |
14
+ | `ListReceivedEmails` | `GET /emails/receiving` | ✅ supported | ✅ | |
13
15
  | `GetReceivedEmail` | `GET /emails/receiving/{email_id}` | ✅ supported | ✅ | |
14
16
  | `ListReceivedEmailAttachments` | `GET /emails/receiving/{email_id}/attachments` | ✅ supported | ✅ | |
15
17
  | `DownloadReceivedAttachment` | `GET /downloads/inbound/{attachment_id}` | ✅ supported | ✅ | |
@@ -1018,7 +1018,40 @@ var decodeBody = (contentType, bytes) => {
1018
1018
  return { kind: "bytes", value: bytes };
1019
1019
  };
1020
1020
  var readBody = async (message) => {
1021
- const bytes = new Uint8Array(await message.arrayBuffer());
1021
+ if (!(message instanceof Request) || !message.body) {
1022
+ if (message instanceof Request)
1023
+ message.signal.throwIfAborted();
1024
+ return decodeBody(message.headers.get("content-type"), new Uint8Array(await message.arrayBuffer()));
1025
+ }
1026
+ message.signal.throwIfAborted();
1027
+ const reader = message.body.getReader();
1028
+ const cancel = () => {
1029
+ void reader.cancel(message.signal.reason).catch(() => {
1030
+ });
1031
+ };
1032
+ message.signal.addEventListener("abort", cancel, { once: true });
1033
+ const chunks = [];
1034
+ let size = 0;
1035
+ try {
1036
+ for (; ; ) {
1037
+ message.signal.throwIfAborted();
1038
+ const { done, value } = await reader.read();
1039
+ message.signal.throwIfAborted();
1040
+ if (done)
1041
+ break;
1042
+ chunks.push(value);
1043
+ size += value.byteLength;
1044
+ }
1045
+ } finally {
1046
+ message.signal.removeEventListener("abort", cancel);
1047
+ reader.releaseLock();
1048
+ }
1049
+ const bytes = new Uint8Array(size);
1050
+ let offset = 0;
1051
+ for (const chunk of chunks) {
1052
+ bytes.set(chunk, offset);
1053
+ offset += chunk.byteLength;
1054
+ }
1022
1055
  return decodeBody(message.headers.get("content-type"), bytes);
1023
1056
  };
1024
1057
 
@@ -1896,7 +1929,42 @@ var verifyOperations = (document2, handlers) => {
1896
1929
  }
1897
1930
  return problems;
1898
1931
  };
1899
- var honoPath = (template) => template.replace(/\{([^}]+)\}/g, ":$1");
1932
+ var escapePattern = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1933
+ var serviceRoute = (template) => {
1934
+ const names = [];
1935
+ let pattern = "", end = 0;
1936
+ for (const match of template.matchAll(/\{([^}]+)\}/g)) {
1937
+ pattern += `${escapePattern(template.slice(end, match.index))}([^/]+?)`;
1938
+ const name = match[1];
1939
+ if (!name)
1940
+ throw new Error(`Invalid OpenAPI route template: ${template}`);
1941
+ names.push(name);
1942
+ end = match.index + match[0].length;
1943
+ }
1944
+ pattern += escapePattern(template.slice(end));
1945
+ const matcher = new RegExp(`^${pattern}$`);
1946
+ const path = template.split("/").map((segment, index) => {
1947
+ if (!segment.includes("{"))
1948
+ return segment;
1949
+ const literal = /^\{([^}]+)\}$/.exec(segment);
1950
+ if (literal)
1951
+ return `:${literal[1]}`;
1952
+ let expression = "", offset = 0;
1953
+ for (const match of segment.matchAll(/\{[^}]+\}/g)) {
1954
+ expression += `${escapePattern(segment.slice(offset, match.index))}[^/]+?`;
1955
+ offset = match.index + match[0].length;
1956
+ }
1957
+ expression += escapePattern(segment.slice(offset));
1958
+ return `:mockingbirdSegment${index}{${expression}}`;
1959
+ }).join("/");
1960
+ return {
1961
+ path,
1962
+ params: (pathname) => {
1963
+ const values = matcher.exec(pathname);
1964
+ return Object.fromEntries(names.map((name, index) => [name, decodeURIComponent(values?.[index + 1] ?? "")]));
1965
+ }
1966
+ };
1967
+ };
1900
1968
  var routeOrder = (a, b) => {
1901
1969
  const sa = a.path.split("/");
1902
1970
  const sb = b.path.split("/");
@@ -1931,6 +1999,7 @@ var createService = (options) => {
1931
1999
  for (const operation of operations) {
1932
2000
  const metadata = operationMetadata(operation.operation);
1933
2001
  const handler = options.handlers[operation.operationId];
2002
+ const registeredRoute = serviceRoute(operation.path);
1934
2003
  const route = async (c) => {
1935
2004
  const request = c.req.raw;
1936
2005
  if (!metadata.supported || !handler) {
@@ -1940,7 +2009,7 @@ var createService = (options) => {
1940
2009
  const context = {
1941
2010
  request,
1942
2011
  url,
1943
- params: c.req.param(),
2012
+ params: registeredRoute.params(url.pathname),
1944
2013
  query: queryOf(url),
1945
2014
  body: await readBody(request),
1946
2015
  sqlite: options.sqlite,
@@ -1952,7 +2021,7 @@ var createService = (options) => {
1952
2021
  const short = await options.before?.(context);
1953
2022
  return withIssues(request, short ?? await handler(context));
1954
2023
  };
1955
- app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
2024
+ app.on(operation.method.toUpperCase(), registeredRoute.path, route);
1956
2025
  }
1957
2026
  return {
1958
2027
  app,
@@ -2421,7 +2490,7 @@ var stateAdminRoutes = (routes) => {
2421
2490
  };
2422
2491
 
2423
2492
  // ../core/dist/version.js
2424
- var PACKAGE_VERSION = true ? "2.1.0" : UNRELEASED_VERSION;
2493
+ var PACKAGE_VERSION = true ? "2.2.0" : UNRELEASED_VERSION;
2425
2494
 
2426
2495
  // ../core/dist/signing.js
2427
2496
  var encoder = new TextEncoder();
@@ -3607,9 +3676,9 @@ var presetRoutes = (presets, runtime, adminPrefix) => ({
3607
3676
  });
3608
3677
 
3609
3678
  // src/generated/openapi.ts
3610
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Resend API (Mockingbird subset)","description":"Stateful mock subset of the Resend email API: send (with \`Idempotency-Key\`), retrieve a sent\\nemail, and the received-email (inbound) endpoints our care-chat hydration reads, including\\nthe attachment download URL. Trimmed from Resend's published API reference to what\\n\`resend@4.8.0\` and our backend call.\\n","version":"1","x-mockingbird-upstream":{"note":"Trimmed from https://resend.com/docs/api-reference (emails/send-email, emails/retrieve-email, receiving/retrieve-received-email, receiving/list-attachments); request/response shapes cross-checked against resend@4.8.0 (dist/index.mjs) and our consumer (send-emails.service.ts, notification-dispatcher email.ts, resend-inbound-email.client.ts)."}},"servers":[{"url":"https://api.resend.com"}],"security":[{"bearerAuth":[]}],"paths":{"/emails":{"post":{"operationId":"SendEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","description":"Replays the first response for the same key and payload (1\u2013256 characters).","schema":{"type":"string","pattern":"^[!-~]{1,256}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailBody"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"email","identity":true},"x-mockingbird-volatile":{"kind":"id"}}}}}}},"400":{"description":"Invalid idempotency key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key reused with another payload, or still in flight","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/{email_id}":{"parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"email","missing":"00000000-0000-4000-8000-000000000000"}}}],"get":{"operationId":"GetEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The sent email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Email"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"GetReceivedEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The received email, with its content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceivedEmail"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}/attachments":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"ListReceivedEmailAttachments","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Attachment metadata with short-lived download URLs","content":{"application/json":{"schema":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","enum":["list"]},"has_more":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivedAttachment"}}}}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/downloads/inbound/{attachment_id}":{"get":{"operationId":"DownloadReceivedAttachment","description":"The \`download_url\` of a received attachment: unauthenticated (it stands in for Resend's signed CDN URL), the raw bytes with their content type and length.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_attachment","missing":"00000000-0000-4000-8000-000000000000"}}}],"responses":{"200":{"description":"The attachment bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown attachment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"\`Authorization: Bearer re_\u2026\`"}},"parameters":{"ReceivedEmailId":{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_email","missing":"00000000-0000-4000-8000-000000000000"}}}},"schemas":{"Address":{"type":"string","description":"\`email@example.com\` or \`Name <email@example.com>\`","pattern":"^([^<>@]* <[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+>|[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+)$","maxLength":120},"Recipients":{"oneOf":[{"$ref":"#/components/schemas/Address"},{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/Address"}}]},"SendEmailBody":{"type":"object","required":["from","to","subject"],"properties":{"from":{"$ref":"#/components/schemas/Address"},"to":{"$ref":"#/components/schemas/Recipients"},"subject":{"type":"string","minLength":1,"maxLength":200},"html":{"type":"string","maxLength":5000},"text":{"type":"string","maxLength":5000},"cc":{"$ref":"#/components/schemas/Recipients"},"bcc":{"$ref":"#/components/schemas/Recipients"},"reply_to":{"$ref":"#/components/schemas/Recipients"},"scheduled_at":{"type":"string","maxLength":60},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":500}},"tags":{"type":"array","maxItems":10,"items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}}}},"attachments":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":100},"content":{"description":"Base64 bytes (or a JSON-serialised Node Buffer).","oneOf":[{"type":"string","maxLength":10000},{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","enum":["Buffer"]},"data":{"type":"array","maxItems":10000,"items":{"type":"integer","minimum":0,"maximum":255}}}}]},"path":{"type":"string","maxLength":500},"content_type":{"type":"string","maxLength":100},"inline_content_id":{"type":"string","maxLength":100}}}}}},"Email":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","bcc","cc","reply_to","last_event"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":"string"},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"bcc":{"type":["array","null"],"items":{"type":"string"}},"cc":{"type":["array","null"],"items":{"type":"string"}},"reply_to":{"type":["array","null"],"items":{"type":"string"}},"last_event":{"type":"string","enum":["delivered","scheduled"]},"scheduled_at":{"type":["string","null"]},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers from the send, with their original names and values."},"tags":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}}}},"ReceivedAttachment":{"type":"object","required":["id","filename","content_type","size","download_url"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"received_attachment","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]},"size":{"type":"integer"},"download_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ReceivedEmail":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","message_id","attachments"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-resource":{"type":"received_email","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"cc":{"type":"array","items":{"type":"string"}},"bcc":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":["string","null"]},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"headers":{"type":"object","additionalProperties":{"type":"string"}},"message_id":{"type":["string","null"]},"attachments":{"type":"array","items":{"type":"object","required":["id","filename","content_type"],"properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]}}}}}},"Error":{"type":"object","description":"Resend's error body; the SDK returns it verbatim as \`error\`.","required":["statusCode","name","message"],"properties":{"statusCode":{"type":"integer"},"name":{"type":"string"},"message":{"type":"string"}}}}}}`);
3611
- var operationIds = ["SendEmail", "GetEmail", "GetReceivedEmail", "ListReceivedEmailAttachments", "DownloadReceivedAttachment"];
3612
- var supportedOperationIds = ["SendEmail", "GetEmail", "GetReceivedEmail", "ListReceivedEmailAttachments", "DownloadReceivedAttachment"];
3679
+ var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Resend API (Mockingbird subset)","description":"Stateful mock subset of the Resend email API: send (with \`Idempotency-Key\`), retrieve a sent\\nemail, and the received-email (inbound) endpoints our care-chat hydration reads, including\\nthe attachment download URL. Trimmed from Resend's published API reference to what\\n\`resend@4.8.0\` and our backend call.\\n","version":"1","x-mockingbird-upstream":{"note":"Trimmed from https://resend.com/docs/api-reference (emails/send-email, emails/retrieve-email, receiving/retrieve-received-email, receiving/list-attachments); request/response shapes cross-checked against resend@4.8.0 (dist/index.mjs) and our consumer (send-emails.service.ts, notification-dispatcher email.ts, resend-inbound-email.client.ts)."}},"servers":[{"url":"https://api.resend.com"}],"security":[{"bearerAuth":[]}],"paths":{"/emails":{"get":{"operationId":"ListEmails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"after","in":"query","schema":{"type":"string"}},{"name":"before","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest-first email metadata","content":{"application/json":{"schema":{"type":"object","required":["object","has_more","data"],"properties":{"object":{"type":"string","enum":["list"]},"has_more":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SentEmailMetadata"}}}}}}},"401":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"SendEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","description":"Replays the first response for the same key and payload (1\u2013256 characters).","schema":{"type":"string","pattern":"^[!-~]{1,256}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailBody"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"email","identity":true},"x-mockingbird-volatile":{"kind":"id"}}}}}}},"400":{"description":"Invalid idempotency key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key reused with another payload, or still in flight","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/{email_id}":{"parameters":[{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"email","missing":"00000000-0000-4000-8000-000000000000"}}}],"get":{"operationId":"GetEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The sent email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Email"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving":{"get":{"operationId":"ListReceivedEmails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"after","in":"query","schema":{"type":"string"}},{"name":"before","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest-first email metadata","content":{"application/json":{"schema":{"type":"object","required":["object","has_more","data"],"properties":{"object":{"type":"string","enum":["list"]},"has_more":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivedEmailMetadata"}}}}}}},"401":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"GetReceivedEmail","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The received email, with its content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceivedEmail"}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/emails/receiving/{email_id}/attachments":{"parameters":[{"$ref":"#/components/parameters/ReceivedEmailId"}],"get":{"operationId":"ListReceivedEmailAttachments","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Attachment metadata with short-lived download URLs","content":{"application/json":{"schema":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","enum":["list"]},"has_more":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivedAttachment"}}}}}}},"401":{"description":"Missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/downloads/inbound/{attachment_id}":{"get":{"operationId":"DownloadReceivedAttachment","description":"The \`download_url\` of a received attachment: unauthenticated (it stands in for Resend's signed CDN URL), the raw bytes with their content type and length.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_attachment","missing":"00000000-0000-4000-8000-000000000000"}}}],"responses":{"200":{"description":"The attachment bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Unknown attachment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"\`Authorization: Bearer re_\u2026\`"}},"parameters":{"ReceivedEmailId":{"name":"email_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"received_email","missing":"00000000-0000-4000-8000-000000000000"}}}},"schemas":{"Address":{"type":"string","description":"\`email@example.com\` or \`Name <email@example.com>\`","pattern":"^([^<>@]* <[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+>|[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\\\.[A-Za-z0-9-]+)+)$","maxLength":120},"Recipients":{"oneOf":[{"$ref":"#/components/schemas/Address"},{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/Address"}}]},"SendEmailBody":{"type":"object","required":["from","to","subject"],"properties":{"from":{"$ref":"#/components/schemas/Address"},"to":{"$ref":"#/components/schemas/Recipients"},"subject":{"type":"string","minLength":1,"maxLength":200},"html":{"type":"string","maxLength":5000},"text":{"type":"string","maxLength":5000},"cc":{"$ref":"#/components/schemas/Recipients"},"bcc":{"$ref":"#/components/schemas/Recipients"},"reply_to":{"$ref":"#/components/schemas/Recipients"},"scheduled_at":{"type":"string","maxLength":60},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":500}},"tags":{"type":"array","maxItems":10,"items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"},"value":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,256}$"}}}},"attachments":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":100},"content":{"description":"Base64 bytes (or a JSON-serialised Node Buffer).","oneOf":[{"type":"string","maxLength":10000},{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","enum":["Buffer"]},"data":{"type":"array","maxItems":10000,"items":{"type":"integer","minimum":0,"maximum":255}}}}]},"path":{"type":"string","maxLength":500},"content_type":{"type":"string","maxLength":100},"inline_content_id":{"type":"string","maxLength":100}}}}}},"SentEmailMetadata":{"type":"object","required":["id","to","from","created_at","subject","bcc","cc","reply_to","message_id","last_event","scheduled_at"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"email","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":"string"},"message_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"bcc":{"type":["array","null"],"items":{"type":"string"}},"cc":{"type":["array","null"],"items":{"type":"string"}},"reply_to":{"type":["array","null"],"items":{"type":"string"}},"last_event":{"type":"string","enum":["delivered","scheduled"]},"scheduled_at":{"type":["string","null"]}}},"ReceivedEmailMetadata":{"type":"object","required":["id","to","from","created_at","subject","bcc","cc","reply_to","message_id","attachments"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"received_email","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":["string","null"]},"message_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"bcc":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"attachments":{"type":"array","items":{"type":"object","required":["id","filename","content_type","content_id","content_disposition","size"],"properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"filename":{"type":"string"},"content_type":{"type":"string"},"content_id":{"type":["string","null"]},"content_disposition":{"type":["string","null"]},"size":{"type":"integer"}}}}}},"Email":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","bcc","cc","reply_to","last_event"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":"string"},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"bcc":{"type":["array","null"],"items":{"type":"string"}},"cc":{"type":["array","null"],"items":{"type":"string"}},"reply_to":{"type":["array","null"],"items":{"type":"string"}},"last_event":{"type":"string","enum":["delivered","scheduled"]},"scheduled_at":{"type":["string","null"]},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers from the send, with their original names and values."},"tags":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}}}},"ReceivedAttachment":{"type":"object","required":["id","filename","content_type","size","download_url"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"received_attachment","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]},"size":{"type":"integer"},"download_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ReceivedEmail":{"type":"object","required":["object","id","to","from","created_at","subject","html","text","message_id","attachments"],"properties":{"object":{"type":"string","enum":["email"]},"id":{"type":"string","x-mockingbird-resource":{"type":"received_email","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"to":{"type":"array","items":{"type":"string"}},"from":{"type":"string"},"cc":{"type":"array","items":{"type":"string"}},"bcc":{"type":"array","items":{"type":"string"}},"reply_to":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"subject":{"type":["string","null"]},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"headers":{"type":"object","additionalProperties":{"type":"string"}},"message_id":{"type":["string","null"]},"attachments":{"type":"array","items":{"type":"object","required":["id","filename","content_type"],"properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"content_disposition":{"type":["string","null"]},"content_id":{"type":["string","null"]}}}}}},"Error":{"type":"object","description":"Resend's error body; the SDK returns it verbatim as \`error\`.","required":["statusCode","name","message"],"properties":{"statusCode":{"type":"integer"},"name":{"type":"string"},"message":{"type":"string"}}}}}}`);
3680
+ var operationIds = ["ListEmails", "SendEmail", "GetEmail", "ListReceivedEmails", "GetReceivedEmail", "ListReceivedEmailAttachments", "DownloadReceivedAttachment"];
3681
+ var supportedOperationIds = ["ListEmails", "SendEmail", "GetEmail", "ListReceivedEmails", "GetReceivedEmail", "ListReceivedEmailAttachments", "DownloadReceivedAttachment"];
3613
3682
 
3614
3683
  // src/state.ts
3615
3684
  var ResendState = class {
@@ -3983,6 +4052,8 @@ var ResendAPI = class {
3983
4052
  this.onOutcome = options.onOutcome;
3984
4053
  this.state = new ResendState(sqlite, namespace);
3985
4054
  const handlers = defineOperations({
4055
+ ListEmails: (context) => this.listEmails(context),
4056
+ ListReceivedEmails: (context) => this.listReceivedEmails(context),
3986
4057
  SendEmail: (context) => this.send(context),
3987
4058
  GetEmail: (context) => this.getEmail(context),
3988
4059
  GetReceivedEmail: (context) => this.getReceived(context),
@@ -4156,6 +4227,68 @@ var ResendAPI = class {
4156
4227
  { ids: { emailId: email.id } }
4157
4228
  );
4158
4229
  }
4230
+ emailPage(context, records, defaultLimit) {
4231
+ const query = context.url.searchParams;
4232
+ const rawLimit = query.get("limit");
4233
+ const limit = rawLimit === null ? Math.max(1, defaultLimit) : Number(rawLimit);
4234
+ const reject = (message) => {
4235
+ throw new HttpError(422, { statusCode: 422, name: "validation_error", message });
4236
+ };
4237
+ if (!Number.isSafeInteger(limit) || limit < 1 || rawLimit !== null && limit > 100) {
4238
+ reject("The pagination limit must be a number between 1 and 100.");
4239
+ }
4240
+ const after = query.get("after");
4241
+ const before = query.get("before");
4242
+ if (after !== null && before !== null) reject("Provide either before or after, not both.");
4243
+ let selected = records;
4244
+ const cursor = after ?? before;
4245
+ if (cursor !== null) {
4246
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(cursor)) {
4247
+ reject("Invalid pagination cursor format.");
4248
+ }
4249
+ const index = records.findIndex((email) => email.id === cursor);
4250
+ selected = index < 0 ? [] : after !== null ? records.slice(index + 1) : records.slice(0, index);
4251
+ }
4252
+ return {
4253
+ object: "list",
4254
+ has_more: selected.length > limit,
4255
+ data: before === null ? selected.slice(0, limit) : selected.slice(-limit)
4256
+ };
4257
+ }
4258
+ listEmails(context) {
4259
+ const records = this.state.outbox.list().reverse().map((email) => ({
4260
+ id: email.id,
4261
+ message_id: `<${email.id}@mock.resend.test>`,
4262
+ to: email.toHeader,
4263
+ from: email.from,
4264
+ created_at: email.createdAt,
4265
+ subject: email.subject,
4266
+ bcc: email.bcc.length > 0 ? email.bcc : null,
4267
+ cc: email.cc.length > 0 ? email.cc : null,
4268
+ reply_to: email.replyTo.length > 0 ? email.replyTo : null,
4269
+ last_event: email.scheduledAt ? "scheduled" : "delivered",
4270
+ scheduled_at: email.scheduledAt
4271
+ }));
4272
+ return jsonRes(200, this.emailPage(context, records, 20));
4273
+ }
4274
+ listReceivedEmails(context) {
4275
+ const records = this.state.received.list({ order: "newest" }).map(({ value: email }) => ({
4276
+ id: email.id,
4277
+ to: email.to,
4278
+ from: email.from,
4279
+ created_at: email.createdAt,
4280
+ subject: email.subject,
4281
+ bcc: email.bcc,
4282
+ cc: email.cc,
4283
+ reply_to: email.replyTo,
4284
+ message_id: email.messageId,
4285
+ attachments: email.attachments.map((attachment) => ({
4286
+ ...this.attachmentMeta(attachment),
4287
+ size: attachment.size
4288
+ }))
4289
+ }));
4290
+ return jsonRes(200, this.emailPage(context, records, records.length));
4291
+ }
4159
4292
  /** Store an inbound email and build the `email.received` event for it. */
4160
4293
  receive(input, origin) {
4161
4294
  const id = this.state.nextId("received");
@@ -4304,4 +4437,4 @@ export {
4304
4437
  bareAddress,
4305
4438
  ResendAPI
4306
4439
  };
4307
- //# sourceMappingURL=chunk-JNTBSBDY.js.map
4440
+ //# sourceMappingURL=chunk-VMXQ2LLE.js.map