@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/LICENSE +115 -0
- package/README.md +1159 -0
- package/dist/api.d.ts +141 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +244 -0
- package/dist/api.js.map +1 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +117 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +417 -0
- package/dist/provider.js.map +1 -0
- package/dist/secrets.d.ts +17 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +52 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities.d.ts +148 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +257 -0
- package/dist/utilities.js.map +1 -0
- package/package.json +53 -0
|
@@ -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"}
|
package/dist/provider.js
ADDED
|
@@ -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"}
|
package/dist/secrets.js
ADDED
|
@@ -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"}
|