@molecule/api-emails-inbound-agentmail 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Minimal AgentMail REST client — only the three calls the inbound bond
3
+ * needs: fetch a message (to hydrate bodies the 1 MB webhook cap omitted),
4
+ * download an attachment (metadata → presigned URL → bytes), and reply.
5
+ * Built on the global `fetch`; no SDK.
6
+ *
7
+ * @see https://docs.agentmail.to/api-reference
8
+ * @see https://docs.agentmail.to/errors
9
+ * @see https://docs.agentmail.to/knowledge-base/rate-limits
10
+ *
11
+ * @module
12
+ */
13
+ import { Buffer } from 'node:buffer';
14
+ import './secrets.js';
15
+ import type { AgentMailAttachmentDownload, AgentMailMessage, AgentMailReplyRequest, AgentMailReplyResponse } from './types.js';
16
+ /**
17
+ * Timeout (ms) for a JSON API call. Bounds a hanging AgentMail endpoint so
18
+ * the webhook handler fails (and AgentMail retries) instead of stalling.
19
+ */
20
+ export declare const API_REQUEST_TIMEOUT_MS = 15000;
21
+ /**
22
+ * Timeout (ms) for downloading one attachment's bytes from its presigned
23
+ * URL. Larger than {@link API_REQUEST_TIMEOUT_MS} because it moves the
24
+ * attachment payload, not a small JSON document.
25
+ */
26
+ export declare const ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 60000;
27
+ /**
28
+ * A non-2xx response from the AgentMail API, carrying the documented error
29
+ * envelope's `code` / `name` / `fix` and — for `429` — the `Retry-After`
30
+ * delay. The message never includes the API key.
31
+ */
32
+ export declare class AgentMailApiError extends Error {
33
+ /** HTTP status AgentMail returned. */
34
+ readonly statusCode: number;
35
+ /** Machine-readable error code (`unknown_api_key`, `rate_limit_exceeded`, …). */
36
+ readonly code: string | undefined;
37
+ /** Legacy error type name from the envelope (`NotFoundError`, …). */
38
+ readonly errorName: string | undefined;
39
+ /** Remediation steps from the envelope, when AgentMail supplied any. */
40
+ readonly fix: string | undefined;
41
+ /** Seconds to wait before retrying, from `Retry-After` (rate limits). */
42
+ readonly retryAfterSeconds: number | undefined;
43
+ /**
44
+ * Builds the error from the HTTP status and the parsed envelope.
45
+ *
46
+ * @param message - Human-readable description.
47
+ * @param details - Status and envelope fields.
48
+ */
49
+ constructor(message: string, details: {
50
+ statusCode: number;
51
+ code?: string;
52
+ errorName?: string;
53
+ fix?: string;
54
+ retryAfterSeconds?: number;
55
+ });
56
+ }
57
+ /**
58
+ * Reads the AgentMail API key from the environment, throwing the tagged
59
+ * `config.notConfigured` error (never revealing any value) when unset.
60
+ *
61
+ * @returns The API key.
62
+ */
63
+ export declare const getApiKey: () => string;
64
+ /**
65
+ * Resolves the API base URL: `AGENTMAIL_BASE_URL` when set (trailing
66
+ * slashes stripped), else {@link DEFAULT_BASE_URL}.
67
+ *
68
+ * @returns The base URL without a trailing slash.
69
+ */
70
+ export declare const getBaseUrl: () => string;
71
+ /**
72
+ * Path of a message resource. Both ids are URL-encoded — AgentMail's
73
+ * `message_id` is the RFC 5322 Message-ID INCLUDING angle brackets and `@`.
74
+ *
75
+ * @param inboxId - The inbox id.
76
+ * @param messageId - The message id, exactly as AgentMail supplied it.
77
+ * @returns `/v0/inboxes/{inbox_id}/messages/{message_id}`.
78
+ */
79
+ export declare const messagePath: (inboxId: string, messageId: string) => string;
80
+ /**
81
+ * Performs one authenticated JSON call against the AgentMail API.
82
+ *
83
+ * @param method - HTTP method.
84
+ * @param path - Path under the base URL (must start with `/`).
85
+ * @param body - Optional JSON request body.
86
+ * @returns The parsed JSON response.
87
+ * @throws {AgentMailApiError} On any non-2xx response.
88
+ * @throws {Error} The tagged `config.notConfigured` error when
89
+ * `AGENTMAIL_API_KEY` is unset.
90
+ */
91
+ export declare const agentMailRequest: <T>(method: "GET" | "POST", path: string, body?: unknown) => Promise<T>;
92
+ /**
93
+ * Fetches a full message — used to hydrate `text` / `html` when the webhook
94
+ * payload omitted them (AgentMail drops both once the payload would exceed
95
+ * 1 MB).
96
+ *
97
+ * @param inboxId - The inbox id.
98
+ * @param messageId - The message id, exactly as AgentMail supplied it.
99
+ * @returns The message.
100
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/get
101
+ */
102
+ export declare const getMessage: (inboxId: string, messageId: string) => Promise<AgentMailMessage>;
103
+ /**
104
+ * Fetches an attachment's metadata + presigned `download_url`.
105
+ *
106
+ * @param inboxId - The inbox id.
107
+ * @param messageId - The message id, exactly as AgentMail supplied it.
108
+ * @param attachmentId - The attachment id from the message's metadata.
109
+ * @returns The attachment metadata and download URL.
110
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/get-attachment
111
+ */
112
+ export declare const getAttachmentDownload: (inboxId: string, messageId: string, attachmentId: string) => Promise<AgentMailAttachmentDownload>;
113
+ /**
114
+ * Downloads an attachment's bytes: resolves the presigned `download_url`
115
+ * via {@link getAttachmentDownload}, then GETs it. The presigned request
116
+ * deliberately carries NO `Authorization` header — the URL is self-
117
+ * authenticating, and object stores reject a request that presents two
118
+ * auth mechanisms at once.
119
+ *
120
+ * @param inboxId - The inbox id.
121
+ * @param messageId - The message id, exactly as AgentMail supplied it.
122
+ * @param attachmentId - The attachment id from the message's metadata.
123
+ * @returns The metadata and the raw bytes.
124
+ * @throws {AgentMailApiError} When either request fails.
125
+ */
126
+ export declare const downloadAttachment: (inboxId: string, messageId: string, attachmentId: string) => Promise<{
127
+ meta: AgentMailAttachmentDownload;
128
+ content: Buffer;
129
+ }>;
130
+ /**
131
+ * Sends a reply to a message from the inbox that received it. AgentMail
132
+ * threads the reply (`In-Reply-To` / `References` / subject) itself.
133
+ *
134
+ * @param inboxId - The inbox id.
135
+ * @param messageId - The message id, exactly as AgentMail supplied it.
136
+ * @param body - The reply.
137
+ * @returns The created message's ids.
138
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/reply
139
+ */
140
+ export declare const replyToMessage: (inboxId: string, messageId: string, body: AgentMailReplyRequest) => Promise<AgentMailReplyResponse>;
141
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAKpC,OAAO,cAAc,CAAA;AAIrB,OAAO,KAAK,EACV,2BAA2B,EAE3B,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,YAAY,CAAA;AAGnB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAA;AAE5C;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAA;AAEpD;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,yEAAyE;IACzE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAA;IAE9C;;;;;OAKG;gBAED,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAA;QAClB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAC3B;CAUJ;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,QAAO,MAQ5B,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAO,MAI7B,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,WAAW,MAAM,KAAG,MAEhE,CAAA;AAuDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAU,CAAC,EACtC,QAAQ,KAAK,GAAG,MAAM,EACtB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,OAAO,CAAC,CAAC,CAmBX,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAU,SAAS,MAAM,EAAE,WAAW,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAE7F,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,EACf,WAAW,MAAM,EACjB,cAAc,MAAM,KACnB,OAAO,CAAC,2BAA2B,CAKrC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,EACf,WAAW,MAAM,EACjB,cAAc,MAAM,KACnB,OAAO,CAAC;IAAE,IAAI,EAAE,2BAA2B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAsBhE,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,WAAW,MAAM,EACjB,MAAM,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CAMhC,CAAA"}
package/dist/api.js ADDED
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Minimal AgentMail REST client — only the three calls the inbound bond
3
+ * needs: fetch a message (to hydrate bodies the 1 MB webhook cap omitted),
4
+ * download an attachment (metadata → presigned URL → bytes), and reply.
5
+ * Built on the global `fetch`; no SDK.
6
+ *
7
+ * @see https://docs.agentmail.to/api-reference
8
+ * @see https://docs.agentmail.to/errors
9
+ * @see https://docs.agentmail.to/knowledge-base/rate-limits
10
+ *
11
+ * @module
12
+ */
13
+ import { Buffer } from 'node:buffer';
14
+ // Side-effect import: registers this bond's secret definitions so the
15
+ // runtime registry is populated even when api.js is imported directly
16
+ // (not through the package barrel).
17
+ import './secrets.js';
18
+ import { configNotConfiguredError } from '@molecule/api-secrets';
19
+ import { DEFAULT_BASE_URL, isRecord, parseRetryAfterSeconds } from './utilities.js';
20
+ /**
21
+ * Timeout (ms) for a JSON API call. Bounds a hanging AgentMail endpoint so
22
+ * the webhook handler fails (and AgentMail retries) instead of stalling.
23
+ */
24
+ export const API_REQUEST_TIMEOUT_MS = 15_000;
25
+ /**
26
+ * Timeout (ms) for downloading one attachment's bytes from its presigned
27
+ * URL. Larger than {@link API_REQUEST_TIMEOUT_MS} because it moves the
28
+ * attachment payload, not a small JSON document.
29
+ */
30
+ export const ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 60_000;
31
+ /**
32
+ * A non-2xx response from the AgentMail API, carrying the documented error
33
+ * envelope's `code` / `name` / `fix` and — for `429` — the `Retry-After`
34
+ * delay. The message never includes the API key.
35
+ */
36
+ export class AgentMailApiError extends Error {
37
+ /** HTTP status AgentMail returned. */
38
+ statusCode;
39
+ /** Machine-readable error code (`unknown_api_key`, `rate_limit_exceeded`, …). */
40
+ code;
41
+ /** Legacy error type name from the envelope (`NotFoundError`, …). */
42
+ errorName;
43
+ /** Remediation steps from the envelope, when AgentMail supplied any. */
44
+ fix;
45
+ /** Seconds to wait before retrying, from `Retry-After` (rate limits). */
46
+ retryAfterSeconds;
47
+ /**
48
+ * Builds the error from the HTTP status and the parsed envelope.
49
+ *
50
+ * @param message - Human-readable description.
51
+ * @param details - Status and envelope fields.
52
+ */
53
+ constructor(message, details) {
54
+ super(message);
55
+ this.name = 'AgentMailApiError';
56
+ this.statusCode = details.statusCode;
57
+ this.code = details.code;
58
+ this.errorName = details.errorName;
59
+ this.fix = details.fix;
60
+ this.retryAfterSeconds = details.retryAfterSeconds;
61
+ }
62
+ }
63
+ /**
64
+ * Reads the AgentMail API key from the environment, throwing the tagged
65
+ * `config.notConfigured` error (never revealing any value) when unset.
66
+ *
67
+ * @returns The API key.
68
+ */
69
+ export const getApiKey = () => {
70
+ const apiKey = process.env.AGENTMAIL_API_KEY;
71
+ if (!apiKey) {
72
+ // Tagged config-missing error → clean 503 + 'config.notConfigured', with the
73
+ // registered definition's description + setup URL (see classifyTaggedError).
74
+ throw configNotConfiguredError('AGENTMAIL_API_KEY', 'inbound email');
75
+ }
76
+ return apiKey;
77
+ };
78
+ /**
79
+ * Resolves the API base URL: `AGENTMAIL_BASE_URL` when set (trailing
80
+ * slashes stripped), else {@link DEFAULT_BASE_URL}.
81
+ *
82
+ * @returns The base URL without a trailing slash.
83
+ */
84
+ export const getBaseUrl = () => {
85
+ const raw = process.env.AGENTMAIL_BASE_URL?.trim();
86
+ const base = raw && raw.length > 0 ? raw : DEFAULT_BASE_URL;
87
+ return base.replace(/\/+$/u, '');
88
+ };
89
+ /**
90
+ * Path of a message resource. Both ids are URL-encoded — AgentMail's
91
+ * `message_id` is the RFC 5322 Message-ID INCLUDING angle brackets and `@`.
92
+ *
93
+ * @param inboxId - The inbox id.
94
+ * @param messageId - The message id, exactly as AgentMail supplied it.
95
+ * @returns `/v0/inboxes/{inbox_id}/messages/{message_id}`.
96
+ */
97
+ export const messagePath = (inboxId, messageId) => {
98
+ return `/v0/inboxes/${encodeURIComponent(inboxId)}/messages/${encodeURIComponent(messageId)}`;
99
+ };
100
+ /**
101
+ * Converts a non-2xx response into an {@link AgentMailApiError}, reading
102
+ * the documented error envelope when the body carries one and falling back
103
+ * to a snippet of the raw body (e.g. the plain-text `413`).
104
+ *
105
+ * @param response - The failed response.
106
+ * @param method - HTTP method, for the message.
107
+ * @param path - Request path, for the message.
108
+ * @returns The mapped error.
109
+ */
110
+ const toApiError = async (response, method, path) => {
111
+ let text = '';
112
+ try {
113
+ text = await response.text();
114
+ }
115
+ catch (_error) {
116
+ // An unreadable error body is still an error response — report the
117
+ // status without a detail rather than masking the failure.
118
+ }
119
+ let envelope = {};
120
+ try {
121
+ const parsed = JSON.parse(text);
122
+ if (isRecord(parsed))
123
+ envelope = parsed;
124
+ }
125
+ catch (_error) {
126
+ // Not JSON (AgentMail's 413 is a bare message) — fall through to the
127
+ // raw-text snippet below.
128
+ }
129
+ const code = typeof envelope.code === 'string' ? envelope.code : undefined;
130
+ const errorName = typeof envelope.name === 'string' ? envelope.name : undefined;
131
+ const fix = typeof envelope.fix === 'string' ? envelope.fix : undefined;
132
+ const detail = typeof envelope.message === 'string' && envelope.message.length > 0
133
+ ? envelope.message
134
+ : text.trim().slice(0, 200) || response.statusText || 'no response body';
135
+ const message = `AgentMail ${method} ${path} failed with HTTP ${String(response.status)}` +
136
+ `${code ? ` (${code})` : ''}: ${detail}${fix ? ` ${fix}` : ''}`;
137
+ return new AgentMailApiError(message, {
138
+ statusCode: response.status,
139
+ code,
140
+ errorName,
141
+ fix,
142
+ retryAfterSeconds: parseRetryAfterSeconds(response.headers.get('retry-after')),
143
+ });
144
+ };
145
+ /**
146
+ * Performs one authenticated JSON call against the AgentMail API.
147
+ *
148
+ * @param method - HTTP method.
149
+ * @param path - Path under the base URL (must start with `/`).
150
+ * @param body - Optional JSON request body.
151
+ * @returns The parsed JSON response.
152
+ * @throws {AgentMailApiError} On any non-2xx response.
153
+ * @throws {Error} The tagged `config.notConfigured` error when
154
+ * `AGENTMAIL_API_KEY` is unset.
155
+ */
156
+ export const agentMailRequest = async (method, path, body) => {
157
+ const apiKey = getApiKey();
158
+ const headers = {
159
+ Authorization: `Bearer ${apiKey}`,
160
+ Accept: 'application/json',
161
+ };
162
+ const init = {
163
+ method,
164
+ headers,
165
+ signal: AbortSignal.timeout(API_REQUEST_TIMEOUT_MS),
166
+ };
167
+ if (body !== undefined) {
168
+ headers['Content-Type'] = 'application/json';
169
+ init.body = JSON.stringify(body);
170
+ }
171
+ const response = await fetch(`${getBaseUrl()}${path}`, init);
172
+ if (!response.ok)
173
+ throw await toApiError(response, method, path);
174
+ return (await response.json());
175
+ };
176
+ /**
177
+ * Fetches a full message — used to hydrate `text` / `html` when the webhook
178
+ * payload omitted them (AgentMail drops both once the payload would exceed
179
+ * 1 MB).
180
+ *
181
+ * @param inboxId - The inbox id.
182
+ * @param messageId - The message id, exactly as AgentMail supplied it.
183
+ * @returns The message.
184
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/get
185
+ */
186
+ export const getMessage = async (inboxId, messageId) => {
187
+ return agentMailRequest('GET', messagePath(inboxId, messageId));
188
+ };
189
+ /**
190
+ * Fetches an attachment's metadata + presigned `download_url`.
191
+ *
192
+ * @param inboxId - The inbox id.
193
+ * @param messageId - The message id, exactly as AgentMail supplied it.
194
+ * @param attachmentId - The attachment id from the message's metadata.
195
+ * @returns The attachment metadata and download URL.
196
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/get-attachment
197
+ */
198
+ export const getAttachmentDownload = async (inboxId, messageId, attachmentId) => {
199
+ return agentMailRequest('GET', `${messagePath(inboxId, messageId)}/attachments/${encodeURIComponent(attachmentId)}`);
200
+ };
201
+ /**
202
+ * Downloads an attachment's bytes: resolves the presigned `download_url`
203
+ * via {@link getAttachmentDownload}, then GETs it. The presigned request
204
+ * deliberately carries NO `Authorization` header — the URL is self-
205
+ * authenticating, and object stores reject a request that presents two
206
+ * auth mechanisms at once.
207
+ *
208
+ * @param inboxId - The inbox id.
209
+ * @param messageId - The message id, exactly as AgentMail supplied it.
210
+ * @param attachmentId - The attachment id from the message's metadata.
211
+ * @returns The metadata and the raw bytes.
212
+ * @throws {AgentMailApiError} When either request fails.
213
+ */
214
+ export const downloadAttachment = async (inboxId, messageId, attachmentId) => {
215
+ const meta = await getAttachmentDownload(inboxId, messageId, attachmentId);
216
+ if (typeof meta.download_url !== 'string' || meta.download_url.length === 0) {
217
+ throw new Error(`AgentMail returned no download_url for attachment ${attachmentId} of message ${messageId}.`);
218
+ }
219
+ const response = await fetch(meta.download_url, {
220
+ method: 'GET',
221
+ signal: AbortSignal.timeout(ATTACHMENT_DOWNLOAD_TIMEOUT_MS),
222
+ });
223
+ if (!response.ok) {
224
+ throw new AgentMailApiError(`AgentMail attachment download for ${attachmentId} failed with HTTP ${String(response.status)}.`, {
225
+ statusCode: response.status,
226
+ retryAfterSeconds: parseRetryAfterSeconds(response.headers.get('retry-after')),
227
+ });
228
+ }
229
+ return { meta, content: Buffer.from(await response.arrayBuffer()) };
230
+ };
231
+ /**
232
+ * Sends a reply to a message from the inbox that received it. AgentMail
233
+ * threads the reply (`In-Reply-To` / `References` / subject) itself.
234
+ *
235
+ * @param inboxId - The inbox id.
236
+ * @param messageId - The message id, exactly as AgentMail supplied it.
237
+ * @param body - The reply.
238
+ * @returns The created message's ids.
239
+ * @see https://docs.agentmail.to/api-reference/inboxes/messages/reply
240
+ */
241
+ export const replyToMessage = async (inboxId, messageId, body) => {
242
+ return agentMailRequest('POST', `${messagePath(inboxId, messageId)}/reply`, body);
243
+ };
244
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Browser guard — `@molecule/api-emails-inbound-agentmail` is SERVER-ONLY.
3
+ *
4
+ * Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
5
+ * Evaluating a server package in a browser bundle is always an import-graph mistake
6
+ * (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
7
+ * ("Buffer is not defined") far from the culprit. Throwing here names the package and
8
+ * the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
9
+ * unaffected: the throw requires browser globals AND the absence of a node runtime.
10
+ */
11
+ const g = globalThis;
12
+ if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
13
+ throw new Error('@molecule/api-emails-inbound-agentmail is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
14
+ 'from server code (a server route/function or your API), or dynamic-import it inside ' +
15
+ 'the server handler — never from components or shared client modules, and never ' +
16
+ 'polyfill Buffer/process to silence this.');
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=browser-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.js","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,GAAG,UAIT,CAAA;AACD,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrF,MAAM,IAAI,KAAK,CACb,iHAAiH;QAC/G,sFAAsF;QACtF,iFAAiF;QACjF,0CAA0C,CAC7C,CAAA;AACH,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * AgentMail inbound-email provider for molecule.dev.
3
+ *
4
+ * Implements `@molecule/api-emails-inbound`'s `InboundEmailProvider`
5
+ * interface against AgentMail's `message.received` webhook. Verifies the
6
+ * Svix signature headers (`svix-id` / `svix-timestamp` / `svix-signature`,
7
+ * HMAC-SHA256 over `id.timestamp.body` keyed by the `whsec_` secret) with
8
+ * replay protection, normalizes the JSON payload, hydrates through the
9
+ * AgentMail API what the webhook leaves out (attachment bytes; bodies over
10
+ * the 1 MB cap), and replies through AgentMail's own reply endpoint. Built
11
+ * on the global `fetch` — no SDK.
12
+ *
13
+ * @remarks
14
+ * - **The webhook route is PUBLIC and needs the RAW body.** Mount it outside
15
+ * any auth middleware and hand `verifySignature()` the exact bytes
16
+ * received — `express.raw({ type: 'application/json' })` on that route, or
17
+ * the body-parser bond's `req.rawBody`. A body that went through
18
+ * `express.json()` and was re-stringified will NOT verify.
19
+ * - **Nothing arrives until BOTH exist at AgentMail: the inbox
20
+ * (`POST /v0/inboxes`) and a webhook registered for it
21
+ * (`POST /v0/webhooks` with `url` + `event_types: ['message.received']`,
22
+ * optionally scoped by `inbox_ids`).** The create-webhook response's
23
+ * `secret` IS `AGENTMAIL_WEBHOOK_SECRET`. Subscribe this URL to
24
+ * `message.received*` only — any other event type (`message.sent`,
25
+ * `message.bounced`, …) makes `parseWebhookPayload()` throw.
26
+ * - `verifySignature()` THROWS the tagged `config.notConfigured` error
27
+ * (→ 503 via the API error middleware) when `AGENTMAIL_WEBHOOK_SECRET` is
28
+ * unset, and resolves `false` for a missing/stale/forged signature. Let the
29
+ * throw propagate — mapping it to the same 401 as a forged webhook hides a
30
+ * misconfigured server behind "invalid signature".
31
+ * - **`parseWebhookPayload()` may call the AgentMail API.** Attachments
32
+ * arrive as metadata only and are downloaded (metadata → presigned
33
+ * `download_url` → bytes); when both `text` and `html` are missing (the
34
+ * 1 MB payload cap) the message is fetched. Both need
35
+ * `AGENTMAIL_API_KEY` (tagged config error if unset) and count against
36
+ * AgentMail's per-key rate limit. A `429` surfaces as an
37
+ * `AgentMailApiError` with `retryAfterSeconds` — let it propagate as a
38
+ * 5xx so AgentMail redelivers later; never swallow it into a 200, which
39
+ * loses the mail. A message with bodies and no attachments makes no
40
+ * network call.
41
+ * - **Replies use AgentMail's reply endpoint, not `@molecule/api-emails`.**
42
+ * The reply is sent from the inbox that received the message and AgentMail
43
+ * threads it itself, so `reply.subject` and `reply.from` are ignored. The
44
+ * inbox is resolved from `AGENTMAIL_INBOX_ID`, else from the in-process
45
+ * record `parseWebhookPayload()` kept — set `AGENTMAIL_INBOX_ID` whenever
46
+ * a reply is sent from a later request or after a restart. When set it
47
+ * also makes `parseWebhookPayload()` reject events for any other inbox.
48
+ * - `InboundEmail.id` is AgentMail's `message_id` verbatim — the Message-ID
49
+ * INCLUDING angle brackets, which is also the path parameter of every
50
+ * per-message endpoint; `messageId` is the same value without brackets.
51
+ * Dedupe on `id`.
52
+ * - The sender field is documented under two spellings (`from` in the API
53
+ * reference, `from_` in the webhooks guide); both are read.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { setProvider } from '@molecule/api-emails-inbound'
58
+ * import { provider as agentMailInbound } from '@molecule/api-emails-inbound-agentmail'
59
+ *
60
+ * setProvider(agentMailInbound)
61
+ * ```
62
+ *
63
+ * @module
64
+ */
65
+ export * from './api.js';
66
+ export * from './browser-guard.js';
67
+ export * from './provider.js';
68
+ export * from './secrets.js';
69
+ export * from './types.js';
70
+ export * from './utilities.js';
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * AgentMail inbound-email provider for molecule.dev.
3
+ *
4
+ * Implements `@molecule/api-emails-inbound`'s `InboundEmailProvider`
5
+ * interface against AgentMail's `message.received` webhook. Verifies the
6
+ * Svix signature headers (`svix-id` / `svix-timestamp` / `svix-signature`,
7
+ * HMAC-SHA256 over `id.timestamp.body` keyed by the `whsec_` secret) with
8
+ * replay protection, normalizes the JSON payload, hydrates through the
9
+ * AgentMail API what the webhook leaves out (attachment bytes; bodies over
10
+ * the 1 MB cap), and replies through AgentMail's own reply endpoint. Built
11
+ * on the global `fetch` — no SDK.
12
+ *
13
+ * @remarks
14
+ * - **The webhook route is PUBLIC and needs the RAW body.** Mount it outside
15
+ * any auth middleware and hand `verifySignature()` the exact bytes
16
+ * received — `express.raw({ type: 'application/json' })` on that route, or
17
+ * the body-parser bond's `req.rawBody`. A body that went through
18
+ * `express.json()` and was re-stringified will NOT verify.
19
+ * - **Nothing arrives until BOTH exist at AgentMail: the inbox
20
+ * (`POST /v0/inboxes`) and a webhook registered for it
21
+ * (`POST /v0/webhooks` with `url` + `event_types: ['message.received']`,
22
+ * optionally scoped by `inbox_ids`).** The create-webhook response's
23
+ * `secret` IS `AGENTMAIL_WEBHOOK_SECRET`. Subscribe this URL to
24
+ * `message.received*` only — any other event type (`message.sent`,
25
+ * `message.bounced`, …) makes `parseWebhookPayload()` throw.
26
+ * - `verifySignature()` THROWS the tagged `config.notConfigured` error
27
+ * (→ 503 via the API error middleware) when `AGENTMAIL_WEBHOOK_SECRET` is
28
+ * unset, and resolves `false` for a missing/stale/forged signature. Let the
29
+ * throw propagate — mapping it to the same 401 as a forged webhook hides a
30
+ * misconfigured server behind "invalid signature".
31
+ * - **`parseWebhookPayload()` may call the AgentMail API.** Attachments
32
+ * arrive as metadata only and are downloaded (metadata → presigned
33
+ * `download_url` → bytes); when both `text` and `html` are missing (the
34
+ * 1 MB payload cap) the message is fetched. Both need
35
+ * `AGENTMAIL_API_KEY` (tagged config error if unset) and count against
36
+ * AgentMail's per-key rate limit. A `429` surfaces as an
37
+ * `AgentMailApiError` with `retryAfterSeconds` — let it propagate as a
38
+ * 5xx so AgentMail redelivers later; never swallow it into a 200, which
39
+ * loses the mail. A message with bodies and no attachments makes no
40
+ * network call.
41
+ * - **Replies use AgentMail's reply endpoint, not `@molecule/api-emails`.**
42
+ * The reply is sent from the inbox that received the message and AgentMail
43
+ * threads it itself, so `reply.subject` and `reply.from` are ignored. The
44
+ * inbox is resolved from `AGENTMAIL_INBOX_ID`, else from the in-process
45
+ * record `parseWebhookPayload()` kept — set `AGENTMAIL_INBOX_ID` whenever
46
+ * a reply is sent from a later request or after a restart. When set it
47
+ * also makes `parseWebhookPayload()` reject events for any other inbox.
48
+ * - `InboundEmail.id` is AgentMail's `message_id` verbatim — the Message-ID
49
+ * INCLUDING angle brackets, which is also the path parameter of every
50
+ * per-message endpoint; `messageId` is the same value without brackets.
51
+ * Dedupe on `id`.
52
+ * - The sender field is documented under two spellings (`from` in the API
53
+ * reference, `from_` in the webhooks guide); both are read.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { setProvider } from '@molecule/api-emails-inbound'
58
+ * import { provider as agentMailInbound } from '@molecule/api-emails-inbound-agentmail'
59
+ *
60
+ * setProvider(agentMailInbound)
61
+ * ```
62
+ *
63
+ * @module
64
+ */
65
+ export * from './api.js';
66
+ export * from './browser-guard.js';
67
+ export * from './provider.js';
68
+ export * from './secrets.js';
69
+ export * from './types.js';
70
+ export * from './utilities.js';
71
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}