@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.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * AgentMail inbound-email provider implementation.
3
+ *
4
+ * AgentMail gives an application its own inbox and POSTs a JSON
5
+ * `message.received` event to a registered webhook URL for every message
6
+ * that lands there. Deliveries are signed by Svix: the `svix-id`,
7
+ * `svix-timestamp` and `svix-signature` headers carry an HMAC-SHA256 over
8
+ * `${id}.${timestamp}.${rawBody}` keyed by the webhook's `whsec_` secret.
9
+ *
10
+ * The webhook is deliberately incomplete on the wire — attachments arrive
11
+ * as metadata only, and `text`/`html` are dropped once the payload would
12
+ * exceed 1 MB — so `parseWebhookPayload` hydrates what is missing through
13
+ * the API (`api.ts`). Replies go through AgentMail's own reply endpoint
14
+ * from the inbox that received the message.
15
+ *
16
+ * @see https://docs.agentmail.to/webhooks
17
+ * @see https://docs.agentmail.to/webhook-verification
18
+ * @see https://docs.svix.com/receiving/verifying-payloads/how-manual
19
+ *
20
+ * @module
21
+ */
22
+ import './secrets.js';
23
+ import type { InboundEmail, InboundEmailProvider, InboundEmailReply, InboundEmailReplyResult } from '@molecule/api-emails-inbound';
24
+ /**
25
+ * Clears the in-process inbox record. Exposed for tests.
26
+ */
27
+ export declare const _resetInboxMemo: () => void;
28
+ /**
29
+ * Verifies an AgentMail (Svix) webhook signature: HMAC-SHA256 over
30
+ * `${svix-id}.${svix-timestamp}.${rawBody}` keyed by the base64-decoded
31
+ * `whsec_` secret, base64-encoded, matched against ANY `v1,…` entry of the
32
+ * `svix-signature` header in constant time. The Standard-Webhooks aliases
33
+ * `webhook-id` / `webhook-timestamp` / `webhook-signature` are accepted
34
+ * too. Timestamps outside the replay window are rejected.
35
+ *
36
+ * `body` MUST be the exact bytes received — a parsed-then-re-serialized
37
+ * JSON body will not verify.
38
+ *
39
+ * Distinguishes SERVER MISCONFIGURATION from a genuinely invalid webhook:
40
+ * an unset `AGENTMAIL_WEBHOOK_SECRET` THROWS the tagged
41
+ * `config.notConfigured` error (mapped by the API error middleware to a
42
+ * clean 503) instead of returning `false`. Missing signature headers, a
43
+ * stale timestamp and a tampered signature all resolve `false` (401) —
44
+ * those ARE the "this request is not from AgentMail" class.
45
+ *
46
+ * @param headers - HTTP headers; the three `svix-*` signing headers.
47
+ * @param body - Raw HTTP request body (JSON bytes, unchanged).
48
+ * @returns `true` when the signature verifies and the timestamp is fresh;
49
+ * `false` for a malformed/stale/forged webhook.
50
+ * @throws {Error} The tagged `config.notConfigured` error when
51
+ * `AGENTMAIL_WEBHOOK_SECRET` is unset — a caller MUST NOT treat this as
52
+ * `false` (that would 401-with-no-trace every inbound webhook instead of
53
+ * surfacing the actionable 503).
54
+ */
55
+ export declare const verifySignature: (headers: Record<string, string | string[] | undefined>, body: Buffer | string) => Promise<boolean>;
56
+ /**
57
+ * Parses an AgentMail `message.received` webhook payload into a normalized
58
+ * {@link InboundEmail}, hydrating through the API whatever the webhook
59
+ * left out:
60
+ *
61
+ * - **Bodies.** When BOTH `text` and `html` are absent (AgentMail drops
62
+ * them once the payload would exceed 1 MB), the message is fetched via
63
+ * `GET /v0/inboxes/{inbox_id}/messages/{message_id}`.
64
+ * - **Attachments.** The webhook carries metadata only; each attachment's
65
+ * bytes are downloaded (metadata → presigned URL → bytes).
66
+ *
67
+ * Either needs `AGENTMAIL_API_KEY`; a message that needs neither makes no
68
+ * network call at all. When `AGENTMAIL_INBOX_ID` is set, an event for any
69
+ * other inbox is rejected.
70
+ *
71
+ * `id` is AgentMail's `message_id` VERBATIM (the Message-ID with its angle
72
+ * brackets — the exact string every per-message endpoint takes as its path
73
+ * parameter); `messageId` is the same value without the brackets, for
74
+ * threading headers. Both are stable across Svix redeliveries, so dedupe
75
+ * on `id`.
76
+ *
77
+ * @param _headers - HTTP headers (unused — AgentMail puts everything in the body).
78
+ * @param body - The raw JSON body, a string, or an already-parsed object.
79
+ * @returns The normalized inbound email.
80
+ * @throws {Error} When the body is not JSON, is not a `message.received*`
81
+ * event, lacks the message ids, or is for a different inbox than
82
+ * `AGENTMAIL_INBOX_ID`.
83
+ * @throws {AgentMailApiError} When hydration (message fetch / attachment
84
+ * download) fails — let it propagate as a 5xx so AgentMail retries.
85
+ * @throws {Error} The tagged `config.notConfigured` error when hydration
86
+ * is needed and `AGENTMAIL_API_KEY` is unset.
87
+ */
88
+ export declare const parseWebhookPayload: (_headers: Record<string, string | string[] | undefined>, body: Buffer | string | Record<string, unknown>) => Promise<InboundEmail>;
89
+ /**
90
+ * Dispatches a reply through AgentMail's reply endpoint from the inbox
91
+ * that received the original message. AgentMail threads the reply itself
92
+ * (`In-Reply-To`, `References`, subject), so `reply.subject` and
93
+ * `reply.from` have no effect — the reply always comes from the inbox,
94
+ * under the original subject.
95
+ *
96
+ * @param email - The original inbound email being replied to.
97
+ * @param reply - The reply payload.
98
+ * @returns The reply dispatch result (`id` = the new message's Message-ID).
99
+ * @throws {Error} When the inbox cannot be resolved (see
100
+ * {@link parseWebhookPayload} for the two sources).
101
+ * @throws {AgentMailApiError} On a non-2xx API response.
102
+ */
103
+ export declare const replyTo: (email: InboundEmail, reply: InboundEmailReply) => Promise<InboundEmailReplyResult>;
104
+ /**
105
+ * Indicates that this provider supports outbound reply dispatch via
106
+ * {@link replyTo}. Replies use AgentMail's own API — no outbound
107
+ * `@molecule/api-emails` transport is involved.
108
+ *
109
+ * @returns Always `true`.
110
+ */
111
+ export declare const supportsReply: () => boolean;
112
+ /**
113
+ * The AgentMail inbound-email provider implementing the
114
+ * {@link InboundEmailProvider} interface.
115
+ */
116
+ export declare const provider: InboundEmailProvider;
117
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EACV,YAAY,EAEZ,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,8BAA8B,CAAA;AA4CrC;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,IAElC,CAAA;AAwDD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,MAAM,MAAM,GAAG,MAAM,KACpB,OAAO,CAAC,OAAO,CAiCjB,CAAA;AAiHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACvD,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9C,OAAO,CAAC,YAAY,CA2DtB,CAAA;AAqBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO,GAClB,OAAO,YAAY,EACnB,OAAO,iBAAiB,KACvB,OAAO,CAAC,uBAAuB,CAyBjC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,QAAO,OAAe,CAAA;AAEhD;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,oBAKtB,CAAA"}
@@ -0,0 +1,417 @@
1
+ /**
2
+ * AgentMail inbound-email provider implementation.
3
+ *
4
+ * AgentMail gives an application its own inbox and POSTs a JSON
5
+ * `message.received` event to a registered webhook URL for every message
6
+ * that lands there. Deliveries are signed by Svix: the `svix-id`,
7
+ * `svix-timestamp` and `svix-signature` headers carry an HMAC-SHA256 over
8
+ * `${id}.${timestamp}.${rawBody}` keyed by the webhook's `whsec_` secret.
9
+ *
10
+ * The webhook is deliberately incomplete on the wire — attachments arrive
11
+ * as metadata only, and `text`/`html` are dropped once the payload would
12
+ * exceed 1 MB — so `parseWebhookPayload` hydrates what is missing through
13
+ * the API (`api.ts`). Replies go through AgentMail's own reply endpoint
14
+ * from the inbox that received the message.
15
+ *
16
+ * @see https://docs.agentmail.to/webhooks
17
+ * @see https://docs.agentmail.to/webhook-verification
18
+ * @see https://docs.svix.com/receiving/verifying-payloads/how-manual
19
+ *
20
+ * @module
21
+ */
22
+ import { createHmac } from 'node:crypto';
23
+ // Side-effect import: registers this bond's secret definitions so the
24
+ // runtime registry is populated even when provider.js is imported directly
25
+ // (not through the package barrel).
26
+ import './secrets.js';
27
+ import { configNotConfiguredError } from '@molecule/api-secrets';
28
+ import { downloadAttachment, getMessage, replyToMessage } from './api.js';
29
+ import { buildSignedContent, decodeWebhookSecret, DEFAULT_REPLAY_WINDOW_SECONDS, getHeader, isRecord, lowercaseHeaderMap, normalizeAddressList, parseJsonBody, parseSignatureHeader, parseTimestamp, safeEqualBase64, unwrapMessageId, } from './utilities.js';
30
+ /** Event-type prefix shared by every inbound-message event AgentMail emits. */
31
+ const INBOUND_EVENT_PREFIX = 'message.received';
32
+ /**
33
+ * Upper bound on the in-process `email.id → inbox_id` record kept by
34
+ * {@link parseWebhookPayload} for {@link replyTo}. Oldest entries are
35
+ * evicted first.
36
+ */
37
+ const INBOX_MEMO_LIMIT = 1000;
38
+ /**
39
+ * `email.id` → `inbox_id` for messages parsed in this process, so a reply
40
+ * issued from the same webhook handler (the agent-auto-responder case)
41
+ * needs no configuration. Cross-process/after-restart replies use
42
+ * `AGENTMAIL_INBOX_ID` instead.
43
+ */
44
+ const inboxByEmailId = new Map();
45
+ /**
46
+ * Clears the in-process inbox record. Exposed for tests.
47
+ */
48
+ export const _resetInboxMemo = () => {
49
+ inboxByEmailId.clear();
50
+ };
51
+ /**
52
+ * Records which inbox a parsed message belongs to, evicting the oldest
53
+ * entry once the record is full.
54
+ *
55
+ * @param emailId - The normalized email's `id`.
56
+ * @param inboxId - AgentMail's `inbox_id`.
57
+ */
58
+ const rememberInbox = (emailId, inboxId) => {
59
+ if (inboxByEmailId.size >= INBOX_MEMO_LIMIT) {
60
+ const oldest = inboxByEmailId.keys().next();
61
+ if (!oldest.done)
62
+ inboxByEmailId.delete(oldest.value);
63
+ }
64
+ inboxByEmailId.set(emailId, inboxId);
65
+ };
66
+ /**
67
+ * Reads the webhook signing secret from the environment, throwing the
68
+ * tagged `config.notConfigured` error (never revealing any value) when unset.
69
+ *
70
+ * @returns The signing secret.
71
+ */
72
+ const getWebhookSecret = () => {
73
+ const secret = process.env.AGENTMAIL_WEBHOOK_SECRET;
74
+ if (!secret) {
75
+ // Tagged config-missing error → clean 503 + 'config.notConfigured', with the
76
+ // registered definition's description + setup URL (see classifyTaggedError).
77
+ throw configNotConfiguredError('AGENTMAIL_WEBHOOK_SECRET', 'inbound email');
78
+ }
79
+ return secret;
80
+ };
81
+ /**
82
+ * Reads the configured replay window (seconds) for inbound webhooks. Falls
83
+ * back to {@link DEFAULT_REPLAY_WINDOW_SECONDS} when unset or invalid.
84
+ *
85
+ * @returns The replay window in seconds.
86
+ */
87
+ const getReplayWindowSeconds = () => {
88
+ const raw = process.env.AGENTMAIL_INBOUND_REPLAY_WINDOW_SECONDS;
89
+ if (!raw)
90
+ return DEFAULT_REPLAY_WINDOW_SECONDS;
91
+ const parsed = Number.parseInt(raw, 10);
92
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REPLAY_WINDOW_SECONDS;
93
+ };
94
+ /**
95
+ * The optional inbox this deployment owns.
96
+ *
97
+ * @returns `AGENTMAIL_INBOX_ID` when set and non-empty, else `undefined`.
98
+ */
99
+ const getConfiguredInboxId = () => {
100
+ const raw = process.env.AGENTMAIL_INBOX_ID?.trim();
101
+ return raw && raw.length > 0 ? raw : undefined;
102
+ };
103
+ /**
104
+ * Verifies an AgentMail (Svix) webhook signature: HMAC-SHA256 over
105
+ * `${svix-id}.${svix-timestamp}.${rawBody}` keyed by the base64-decoded
106
+ * `whsec_` secret, base64-encoded, matched against ANY `v1,…` entry of the
107
+ * `svix-signature` header in constant time. The Standard-Webhooks aliases
108
+ * `webhook-id` / `webhook-timestamp` / `webhook-signature` are accepted
109
+ * too. Timestamps outside the replay window are rejected.
110
+ *
111
+ * `body` MUST be the exact bytes received — a parsed-then-re-serialized
112
+ * JSON body will not verify.
113
+ *
114
+ * Distinguishes SERVER MISCONFIGURATION from a genuinely invalid webhook:
115
+ * an unset `AGENTMAIL_WEBHOOK_SECRET` THROWS the tagged
116
+ * `config.notConfigured` error (mapped by the API error middleware to a
117
+ * clean 503) instead of returning `false`. Missing signature headers, a
118
+ * stale timestamp and a tampered signature all resolve `false` (401) —
119
+ * those ARE the "this request is not from AgentMail" class.
120
+ *
121
+ * @param headers - HTTP headers; the three `svix-*` signing headers.
122
+ * @param body - Raw HTTP request body (JSON bytes, unchanged).
123
+ * @returns `true` when the signature verifies and the timestamp is fresh;
124
+ * `false` for a malformed/stale/forged webhook.
125
+ * @throws {Error} The tagged `config.notConfigured` error when
126
+ * `AGENTMAIL_WEBHOOK_SECRET` is unset — a caller MUST NOT treat this as
127
+ * `false` (that would 401-with-no-trace every inbound webhook instead of
128
+ * surfacing the actionable 503).
129
+ */
130
+ export const verifySignature = async (headers, body) => {
131
+ // Deliberately NOT caught here: an unconfigured secret is a server
132
+ // misconfiguration, not "signature invalid," and must propagate as the
133
+ // tagged config error so the caller (the API error middleware) can 503
134
+ // instead of silently 401ing every webhook. See @throws above.
135
+ const secret = getWebhookSecret();
136
+ const id = getHeader(headers, 'svix-id') ?? getHeader(headers, 'webhook-id');
137
+ const timestamp = getHeader(headers, 'svix-timestamp') ?? getHeader(headers, 'webhook-timestamp');
138
+ const signatureHeader = getHeader(headers, 'svix-signature') ?? getHeader(headers, 'webhook-signature');
139
+ if (!id || !timestamp || !signatureHeader)
140
+ return false;
141
+ // Reject stale timestamps to defend against replay.
142
+ if (!/^\d+$/u.test(timestamp))
143
+ return false;
144
+ const ts = Number.parseInt(timestamp, 10);
145
+ const nowSeconds = Math.floor(Date.now() / 1000);
146
+ if (Math.abs(nowSeconds - ts) > getReplayWindowSeconds())
147
+ return false;
148
+ const key = decodeWebhookSecret(secret);
149
+ if (key.length === 0)
150
+ return false;
151
+ const expected = createHmac('sha256', key)
152
+ .update(buildSignedContent(id, timestamp, body))
153
+ .digest('base64');
154
+ // Compare against every v1 entry: Svix sends several during a secret
155
+ // rotation. `some` short-circuits on the first MATCH only, and each
156
+ // comparison is itself constant-time.
157
+ return parseSignatureHeader(signatureHeader).some((candidate) => safeEqualBase64(expected, candidate));
158
+ };
159
+ /**
160
+ * Type guard for the subset of {@link AgentMailMessage} this bond requires
161
+ * (`inbox_id` + `message_id` strings). Every other field is optional in the
162
+ * schema and read defensively downstream.
163
+ *
164
+ * @param value - Candidate `message` object.
165
+ * @returns `true` when `value` carries the required ids.
166
+ */
167
+ const isAgentMailMessage = (value) => {
168
+ if (!isRecord(value))
169
+ return false;
170
+ return (typeof value.inbox_id === 'string' &&
171
+ value.inbox_id.length > 0 &&
172
+ typeof value.message_id === 'string' &&
173
+ value.message_id.length > 0);
174
+ };
175
+ /**
176
+ * Validates a parsed webhook body as an inbound-message event.
177
+ *
178
+ * @param parsed - The parsed JSON body.
179
+ * @returns The typed event.
180
+ * @throws {Error} When the body is not an object, has no `event_type`, is
181
+ * an event other than `message.received*`, or lacks a usable `message`.
182
+ */
183
+ const asInboundEvent = (parsed) => {
184
+ if (!isRecord(parsed)) {
185
+ throw new Error('AgentMail webhook payload is not a JSON object.');
186
+ }
187
+ const eventType = parsed.event_type;
188
+ if (typeof eventType !== 'string' || eventType.length === 0) {
189
+ throw new Error('AgentMail webhook payload has no `event_type`.');
190
+ }
191
+ if (!eventType.startsWith(INBOUND_EVENT_PREFIX)) {
192
+ throw new Error(`AgentMail event "${eventType}" is not an inbound message — register this webhook URL for ` +
193
+ `\`message.received\` (and, if wanted, its .spam/.blocked/.unauthenticated variants) only.`);
194
+ }
195
+ const message = parsed.message;
196
+ if (!isAgentMailMessage(message)) {
197
+ throw new Error('AgentMail webhook payload has no `message` with string `inbox_id` and `message_id`.');
198
+ }
199
+ const event = { event_type: eventType, message };
200
+ if (typeof parsed.event_id === 'string')
201
+ event.event_id = parsed.event_id;
202
+ if (typeof parsed.type === 'string')
203
+ event.type = parsed.type;
204
+ return event;
205
+ };
206
+ /**
207
+ * Maps one attachment's metadata + downloaded bytes onto the normalized
208
+ * shape.
209
+ *
210
+ * @param meta - Attachment metadata (webhook or API).
211
+ * @param contentBase64 - The downloaded bytes, base64-encoded.
212
+ * @returns The normalized attachment.
213
+ */
214
+ const toInboundAttachment = (meta, contentBase64) => {
215
+ const attachment = {
216
+ name: typeof meta.filename === 'string' && meta.filename.length > 0
217
+ ? meta.filename
218
+ : meta.attachment_id,
219
+ contentType: typeof meta.content_type === 'string' && meta.content_type.length > 0
220
+ ? meta.content_type
221
+ : 'application/octet-stream',
222
+ contentBase64,
223
+ };
224
+ if (typeof meta.size === 'number' && Number.isFinite(meta.size))
225
+ attachment.sizeBytes = meta.size;
226
+ if (typeof meta.content_id === 'string' && meta.content_id.length > 0) {
227
+ attachment.contentId = meta.content_id;
228
+ }
229
+ return attachment;
230
+ };
231
+ /**
232
+ * Downloads every attachment of a message — the webhook carries metadata
233
+ * only. Sequential on purpose: AgentMail rate-limits per API key, and a
234
+ * `429` here must surface (so the webhook is retried later), not fan out.
235
+ *
236
+ * @param message - The message whose attachments to fetch.
237
+ * @returns Normalized attachments (empty when the message has none).
238
+ */
239
+ const fetchAttachments = async (message) => {
240
+ const out = [];
241
+ const metas = Array.isArray(message.attachments) ? message.attachments : [];
242
+ for (const meta of metas) {
243
+ if (!isRecord(meta) ||
244
+ typeof meta.attachment_id !== 'string' ||
245
+ meta.attachment_id.length === 0) {
246
+ continue;
247
+ }
248
+ const { content } = await downloadAttachment(message.inbox_id, message.message_id, meta.attachment_id);
249
+ out.push(toInboundAttachment(meta, content.toString('base64')));
250
+ }
251
+ return out;
252
+ };
253
+ /**
254
+ * Parses an AgentMail `message.received` webhook payload into a normalized
255
+ * {@link InboundEmail}, hydrating through the API whatever the webhook
256
+ * left out:
257
+ *
258
+ * - **Bodies.** When BOTH `text` and `html` are absent (AgentMail drops
259
+ * them once the payload would exceed 1 MB), the message is fetched via
260
+ * `GET /v0/inboxes/{inbox_id}/messages/{message_id}`.
261
+ * - **Attachments.** The webhook carries metadata only; each attachment's
262
+ * bytes are downloaded (metadata → presigned URL → bytes).
263
+ *
264
+ * Either needs `AGENTMAIL_API_KEY`; a message that needs neither makes no
265
+ * network call at all. When `AGENTMAIL_INBOX_ID` is set, an event for any
266
+ * other inbox is rejected.
267
+ *
268
+ * `id` is AgentMail's `message_id` VERBATIM (the Message-ID with its angle
269
+ * brackets — the exact string every per-message endpoint takes as its path
270
+ * parameter); `messageId` is the same value without the brackets, for
271
+ * threading headers. Both are stable across Svix redeliveries, so dedupe
272
+ * on `id`.
273
+ *
274
+ * @param _headers - HTTP headers (unused — AgentMail puts everything in the body).
275
+ * @param body - The raw JSON body, a string, or an already-parsed object.
276
+ * @returns The normalized inbound email.
277
+ * @throws {Error} When the body is not JSON, is not a `message.received*`
278
+ * event, lacks the message ids, or is for a different inbox than
279
+ * `AGENTMAIL_INBOX_ID`.
280
+ * @throws {AgentMailApiError} When hydration (message fetch / attachment
281
+ * download) fails — let it propagate as a 5xx so AgentMail retries.
282
+ * @throws {Error} The tagged `config.notConfigured` error when hydration
283
+ * is needed and `AGENTMAIL_API_KEY` is unset.
284
+ */
285
+ export const parseWebhookPayload = async (_headers, body) => {
286
+ let parsed;
287
+ try {
288
+ parsed = parseJsonBody(body);
289
+ }
290
+ catch (error) {
291
+ throw new Error('AgentMail webhook body is not valid JSON.', { cause: error });
292
+ }
293
+ const event = asInboundEvent(parsed);
294
+ let message = event.message;
295
+ const configuredInboxId = getConfiguredInboxId();
296
+ if (configuredInboxId !== undefined && message.inbox_id !== configuredInboxId) {
297
+ throw new Error(`AgentMail webhook is for inbox "${message.inbox_id}", not the configured AGENTMAIL_INBOX_ID — ` +
298
+ 'scope the webhook to this inbox (inbox_ids on the webhook) or unset AGENTMAIL_INBOX_ID.');
299
+ }
300
+ if (typeof message.text !== 'string' && typeof message.html !== 'string') {
301
+ // Payload cap (1 MB): AgentMail omitted both bodies — fetch them. The
302
+ // ids come from the webhook (authoritative); everything else from the
303
+ // full message.
304
+ const full = await getMessage(message.inbox_id, message.message_id);
305
+ message = { ...message, ...full, inbox_id: message.inbox_id, message_id: message.message_id };
306
+ }
307
+ const from = normalizeAddressList(message.from ?? message.from_)[0] ?? '';
308
+ const subject = typeof message.subject === 'string' ? message.subject : '';
309
+ const messageId = unwrapMessageId(message.message_id);
310
+ const email = {
311
+ id: message.message_id,
312
+ from,
313
+ to: normalizeAddressList(message.to),
314
+ subject,
315
+ headers: lowercaseHeaderMap(message.headers),
316
+ receivedAt: parseTimestamp(message.timestamp) ?? parseTimestamp(message.created_at) ?? new Date(),
317
+ };
318
+ const cc = normalizeAddressList(message.cc);
319
+ if (cc.length > 0)
320
+ email.cc = cc;
321
+ if (typeof message.text === 'string')
322
+ email.textBody = message.text;
323
+ if (typeof message.html === 'string')
324
+ email.htmlBody = message.html;
325
+ const attachments = await fetchAttachments(message);
326
+ if (attachments.length > 0)
327
+ email.attachments = attachments;
328
+ if (messageId)
329
+ email.messageId = messageId;
330
+ const inReplyTo = unwrapMessageId(message.in_reply_to);
331
+ if (inReplyTo)
332
+ email.inReplyTo = inReplyTo;
333
+ const references = normalizeAddressList(message.references)
334
+ .map((ref) => unwrapMessageId(ref))
335
+ .filter((ref) => ref !== undefined);
336
+ if (references.length > 0)
337
+ email.references = references;
338
+ rememberInbox(email.id, message.inbox_id);
339
+ return email;
340
+ };
341
+ /**
342
+ * Resolves the inbox a reply must be sent from: `AGENTMAIL_INBOX_ID` when
343
+ * set, else the inbox recorded when this process parsed the message.
344
+ *
345
+ * @param email - The original inbound email.
346
+ * @returns The inbox id.
347
+ * @throws {Error} When neither source knows the inbox.
348
+ */
349
+ const resolveInboxId = (email) => {
350
+ const inboxId = getConfiguredInboxId() ?? inboxByEmailId.get(email.id);
351
+ if (!inboxId) {
352
+ throw new Error(`Cannot reply to AgentMail message ${email.id}: its inbox is unknown in this process. ` +
353
+ 'Set AGENTMAIL_INBOX_ID to the inbox_id that receives mail (required for replies sent from a later request or after a restart).');
354
+ }
355
+ return inboxId;
356
+ };
357
+ /**
358
+ * Dispatches a reply through AgentMail's reply endpoint from the inbox
359
+ * that received the original message. AgentMail threads the reply itself
360
+ * (`In-Reply-To`, `References`, subject), so `reply.subject` and
361
+ * `reply.from` have no effect — the reply always comes from the inbox,
362
+ * under the original subject.
363
+ *
364
+ * @param email - The original inbound email being replied to.
365
+ * @param reply - The reply payload.
366
+ * @returns The reply dispatch result (`id` = the new message's Message-ID).
367
+ * @throws {Error} When the inbox cannot be resolved (see
368
+ * {@link parseWebhookPayload} for the two sources).
369
+ * @throws {AgentMailApiError} On a non-2xx API response.
370
+ */
371
+ export const replyTo = async (email, reply) => {
372
+ const inboxId = resolveInboxId(email);
373
+ const request = {};
374
+ if (email.from.length > 0)
375
+ request.to = email.from;
376
+ if (reply.textBody !== undefined)
377
+ request.text = reply.textBody;
378
+ if (reply.htmlBody !== undefined)
379
+ request.html = reply.htmlBody;
380
+ if (reply.headers && Object.keys(reply.headers).length > 0)
381
+ request.headers = { ...reply.headers };
382
+ if (reply.attachments && reply.attachments.length > 0) {
383
+ request.attachments = reply.attachments.map((a) => {
384
+ const attachment = {
385
+ filename: a.name,
386
+ content_type: a.contentType,
387
+ content: a.contentBase64,
388
+ };
389
+ if (a.contentId !== undefined) {
390
+ attachment.content_id = a.contentId;
391
+ attachment.content_disposition = 'inline';
392
+ }
393
+ return attachment;
394
+ });
395
+ }
396
+ const result = await replyToMessage(inboxId, email.id, request);
397
+ return { id: result.message_id };
398
+ };
399
+ /**
400
+ * Indicates that this provider supports outbound reply dispatch via
401
+ * {@link replyTo}. Replies use AgentMail's own API — no outbound
402
+ * `@molecule/api-emails` transport is involved.
403
+ *
404
+ * @returns Always `true`.
405
+ */
406
+ export const supportsReply = () => true;
407
+ /**
408
+ * The AgentMail inbound-email provider implementing the
409
+ * {@link InboundEmailProvider} interface.
410
+ */
411
+ export const provider = {
412
+ parseWebhookPayload,
413
+ verifySignature,
414
+ replyTo,
415
+ supportsReply,
416
+ };
417
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * AgentMail secret definitions (inbound webhooks + API) — self-registered at
3
+ * import time so the runtime secrets registry (`@molecule/api-secrets`) can
4
+ * drive boot-time configuration reports and actionable "not configured"
5
+ * errors.
6
+ *
7
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
8
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
9
+ * packages sharing a key register byte-identical definitions and
10
+ * registration order never matters.
11
+ *
12
+ * @module
13
+ */
14
+ import type { SecretDefinition } from '@molecule/api-secrets';
15
+ /** Secret definitions required by the AgentMail inbound-email bond. */
16
+ export declare const agentMailInboundSecretDefinitions: SecretDefinition[];
17
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,uEAAuE;AACvE,eAAO,MAAM,iCAAiC,EAAE,gBAAgB,EAuC/D,CAAA"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * AgentMail secret definitions (inbound webhooks + API) — self-registered at
3
+ * import time so the runtime secrets registry (`@molecule/api-secrets`) can
4
+ * drive boot-time configuration reports and actionable "not configured"
5
+ * errors.
6
+ *
7
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
8
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
9
+ * packages sharing a key register byte-identical definitions and
10
+ * registration order never matters.
11
+ *
12
+ * @module
13
+ */
14
+ import { registerSecrets } from '@molecule/api-secrets';
15
+ /** Secret definitions required by the AgentMail inbound-email bond. */
16
+ export const agentMailInboundSecretDefinitions = [
17
+ {
18
+ key: 'AGENTMAIL_API_KEY',
19
+ description: 'AgentMail API key — AgentMail console → create an account and generate an API key from the dashboard.',
20
+ helpUrl: 'https://console.agentmail.to',
21
+ required: true,
22
+ example: 'am_...',
23
+ },
24
+ {
25
+ key: 'AGENTMAIL_WEBHOOK_SECRET',
26
+ description: 'AgentMail webhook signing secret — The `secret` returned when the webhook is created (also readable from the console); verifies inbound webhook signatures.',
27
+ helpUrl: 'https://docs.agentmail.to/webhook-verification',
28
+ required: true,
29
+ example: 'whsec_...',
30
+ },
31
+ {
32
+ key: 'AGENTMAIL_INBOX_ID',
33
+ description: 'AgentMail inbox ID — The `inbox_id` of the inbox that receives mail; when set, webhooks for other inboxes are rejected and replies are sent from this inbox.',
34
+ helpUrl: 'https://docs.agentmail.to/api-reference/inboxes/create',
35
+ required: false,
36
+ },
37
+ {
38
+ key: 'AGENTMAIL_BASE_URL',
39
+ description: 'AgentMail API base URL — Only set for a regional endpoint (EU: https://api.agentmail.eu); the default is fine.',
40
+ required: false,
41
+ example: 'https://api.agentmail.to',
42
+ default: 'https://api.agentmail.to',
43
+ },
44
+ {
45
+ key: 'AGENTMAIL_INBOUND_REPLAY_WINDOW_SECONDS',
46
+ description: 'AgentMail inbound replay window — Max age (seconds) of accepted inbound webhook signatures — replay protection; the default is fine.',
47
+ required: false,
48
+ example: '300',
49
+ },
50
+ ];
51
+ registerSecrets(agentMailInboundSecretDefinitions);
52
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,uEAAuE;AACvE,MAAM,CAAC,MAAM,iCAAiC,GAAuB;IACnE;QACE,GAAG,EAAE,mBAAmB;QACxB,WAAW,EACT,uGAAuG;QACzG,OAAO,EAAE,8BAA8B;QACvC,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,QAAQ;KAClB;IACD;QACE,GAAG,EAAE,0BAA0B;QAC/B,WAAW,EACT,6JAA6J;QAC/J,OAAO,EAAE,gDAAgD;QACzD,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,WAAW;KACrB;IACD;QACE,GAAG,EAAE,oBAAoB;QACzB,WAAW,EACT,8JAA8J;QAChK,OAAO,EAAE,wDAAwD;QACjE,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,oBAAoB;QACzB,WAAW,EACT,gHAAgH;QAClH,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,0BAA0B;QACnC,OAAO,EAAE,0BAA0B;KACpC;IACD;QACE,GAAG,EAAE,yCAAyC;QAC9C,WAAW,EACT,sIAAsI;QACxI,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,KAAK;KACf;CACF,CAAA;AAED,eAAe,CAAC,iCAAiC,CAAC,CAAA"}