@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
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
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|
package/dist/index.d.ts
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.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"}
|