@molecule/api-emails-inbound-ses 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,378 @@
1
+ /**
2
+ * AWS SES inbound-email provider implementation.
3
+ *
4
+ * SES Inbound parses received mail upstream and either delivers the message
5
+ * to S3 or publishes a notification (with the parsed `mail` metadata and
6
+ * optional base64-encoded raw RFC 822 `content`) to an SNS topic. This
7
+ * bond handles the SNS-notification path: an HTTPS endpoint subscribed to
8
+ * the topic receives JSON, validates the SNS signature, and parses the
9
+ * embedded `content` (RFC 822) into a normalized `InboundEmail`.
10
+ *
11
+ * Outbound replies compose onto the bonded `@molecule/api-emails`
12
+ * transport (typically `@molecule/api-emails-ses`); we never reimplement
13
+ * SMTP / SES SendEmail here.
14
+ *
15
+ * @see https://docs.aws.amazon.com/ses/latest/dg/receiving-email-notifications.html
16
+ * @see https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
17
+ *
18
+ * @module
19
+ */
20
+ import { Buffer } from 'node:buffer';
21
+ import { createPublicKey, createVerify } from 'node:crypto';
22
+ // Side-effect import: registers this bond's secret definitions so the
23
+ // runtime registry is populated even when provider.js is imported directly
24
+ // (not through the package barrel).
25
+ import './secrets.js';
26
+ import { sendMail } from '@molecule/api-emails';
27
+ import { base64ToBuffer, buildSnsCanonicalString, isAllowedSigningCertUrl, parseJsonBody, parseRawMimeContent, splitReferences, unwrapMessageId, } from './utilities.js';
28
+ /**
29
+ * Cache of fetched SNS signing certificate PEM bodies, keyed by
30
+ * `SigningCertURL`. AWS rotates these infrequently and serves them from a
31
+ * CloudFront-fronted endpoint, so caching reduces per-request latency
32
+ * without compromising security (the URL itself is allowlisted).
33
+ */
34
+ const certCache = new Map();
35
+ /**
36
+ * Timeout (ms) for the (uncached) signing-certificate fetch. Bounds the
37
+ * worst case so a hanging/slow cert endpoint fails fast into a `false`
38
+ * verification result instead of stalling the inbound webhook handler for
39
+ * `fetch`'s much longer default timeout — which otherwise makes inbound
40
+ * email processing look "frozen" rather than failing (see
41
+ * integration-audit-findings.md → [email] hostile-default).
42
+ */
43
+ const CERT_FETCH_TIMEOUT_MS = 5000;
44
+ /**
45
+ * Resets the cached signing certificates. Exposed for tests.
46
+ */
47
+ export const _resetSigningCertCache = () => {
48
+ certCache.clear();
49
+ };
50
+ /**
51
+ * Fetches an SNS signing certificate. Validates the URL against the
52
+ * allowlist before performing any network I/O.
53
+ *
54
+ * @param url - The `SigningCertURL` from the SNS payload.
55
+ * @returns The PEM-encoded certificate body, or `undefined` when the URL
56
+ * is not allowlisted, the fetch fails, or it does not complete within
57
+ * {@link CERT_FETCH_TIMEOUT_MS}.
58
+ */
59
+ const fetchSigningCert = async (url) => {
60
+ if (!isAllowedSigningCertUrl(url))
61
+ return undefined;
62
+ const cached = certCache.get(url);
63
+ if (cached !== undefined)
64
+ return cached;
65
+ try {
66
+ const response = await fetch(url, { signal: AbortSignal.timeout(CERT_FETCH_TIMEOUT_MS) });
67
+ if (!response.ok)
68
+ return undefined;
69
+ const pem = await response.text();
70
+ if (!pem.includes('BEGIN CERTIFICATE'))
71
+ return undefined;
72
+ certCache.set(url, pem);
73
+ return pem;
74
+ }
75
+ catch (_error) {
76
+ // Best-effort fetch — network failures, including our own abort-on-timeout,
77
+ // are surfaced as `undefined`; the caller treats a missing cert as a
78
+ // signature-verification failure (false), never a hang.
79
+ return undefined;
80
+ }
81
+ };
82
+ /**
83
+ * Type guard for the subset of {@link SnsNotificationPayload} fields we
84
+ * rely on. Returns `false` for malformed payloads without throwing.
85
+ *
86
+ * @param value - Candidate parsed JSON body.
87
+ * @returns `true` when `value` looks like an SNS payload.
88
+ */
89
+ const isSnsPayload = (value) => {
90
+ if (!value || typeof value !== 'object')
91
+ return false;
92
+ const v = value;
93
+ return (typeof v.Type === 'string' &&
94
+ typeof v.MessageId === 'string' &&
95
+ typeof v.Message === 'string' &&
96
+ typeof v.Timestamp === 'string' &&
97
+ typeof v.Signature === 'string' &&
98
+ typeof v.SigningCertURL === 'string' &&
99
+ typeof v.SignatureVersion === 'string');
100
+ };
101
+ /**
102
+ * Verifies the signature of an SNS notification payload. Implements the
103
+ * AWS SNS signature-verification flow:
104
+ *
105
+ * 1. Parse the JSON body.
106
+ * 2. Reject if `SigningCertURL` is not from an allowlisted host.
107
+ * 3. Fetch the X.509 certificate from `SigningCertURL`.
108
+ * 4. Build the canonical string per AWS docs (field order varies by
109
+ * `Type`).
110
+ * 5. Verify the base64-decoded `Signature` against the canonical string
111
+ * using SHA1 (`SignatureVersion === '1'`) or SHA256
112
+ * (`SignatureVersion === '2'`).
113
+ * 6. When `AWS_SES_INBOUND_TOPIC_ARN` is set, also verify the payload's
114
+ * `TopicArn` matches.
115
+ *
116
+ * Errors NEVER leak signing material; failures simply return `false`.
117
+ *
118
+ * @param _headers - HTTP headers (unused — SNS signs the body).
119
+ * @param body - Raw HTTP request body (JSON).
120
+ * @returns `true` when the signature is valid, `false` otherwise.
121
+ */
122
+ export const verifySignature = async (_headers, body) => {
123
+ let parsed;
124
+ try {
125
+ parsed = parseJsonBody(body);
126
+ }
127
+ catch (_error) {
128
+ // Malformed body — not a valid SNS notification; return false (no signature).
129
+ return false;
130
+ }
131
+ if (!isSnsPayload(parsed))
132
+ return false;
133
+ const expectedTopic = process.env.AWS_SES_INBOUND_TOPIC_ARN;
134
+ if (expectedTopic && parsed.TopicArn !== expectedTopic)
135
+ return false;
136
+ if (!isAllowedSigningCertUrl(parsed.SigningCertURL))
137
+ return false;
138
+ const pem = await fetchSigningCert(parsed.SigningCertURL);
139
+ if (!pem)
140
+ return false;
141
+ const algorithm = parsed.SignatureVersion === '2'
142
+ ? 'RSA-SHA256'
143
+ : parsed.SignatureVersion === '1'
144
+ ? 'RSA-SHA1'
145
+ : undefined;
146
+ if (!algorithm)
147
+ return false;
148
+ let canonical;
149
+ try {
150
+ canonical = buildSnsCanonicalString(parsed);
151
+ }
152
+ catch (_error) {
153
+ // Malformed payload fields — cannot build the canonical string; treat as invalid.
154
+ return false;
155
+ }
156
+ let signatureBuffer;
157
+ try {
158
+ signatureBuffer = Buffer.from(parsed.Signature, 'base64');
159
+ }
160
+ catch (_error) {
161
+ // Invalid base64 signature value — cannot decode; treat as invalid.
162
+ return false;
163
+ }
164
+ if (signatureBuffer.length === 0)
165
+ return false;
166
+ try {
167
+ const publicKey = createPublicKey(pem);
168
+ const verifier = createVerify(algorithm);
169
+ verifier.update(canonical, 'utf8');
170
+ verifier.end();
171
+ return verifier.verify(publicKey, signatureBuffer);
172
+ }
173
+ catch (_error) {
174
+ // Crypto errors (bad PEM, unsupported key, verify failure) — treat as invalid signature.
175
+ return false;
176
+ }
177
+ };
178
+ /**
179
+ * Maps an SES `commonHeaders` block onto the normalized header dict.
180
+ *
181
+ * @param mail - The SES `mail` object.
182
+ * @returns A `Record` of lowercased header names to values.
183
+ */
184
+ const headersFromSesMail = (mail) => {
185
+ const out = {};
186
+ if (Array.isArray(mail.headers)) {
187
+ for (const h of mail.headers) {
188
+ if (!h || typeof h.name !== 'string' || typeof h.value !== 'string')
189
+ continue;
190
+ const lower = h.name.toLowerCase();
191
+ const existing = out[lower];
192
+ if (existing === undefined) {
193
+ out[lower] = h.value;
194
+ }
195
+ else if (Array.isArray(existing)) {
196
+ existing.push(h.value);
197
+ }
198
+ else {
199
+ out[lower] = [existing, h.value];
200
+ }
201
+ }
202
+ }
203
+ const ch = mail.commonHeaders;
204
+ if (ch) {
205
+ if (typeof ch.subject === 'string' && out.subject === undefined)
206
+ out.subject = ch.subject;
207
+ if (typeof ch.messageId === 'string' && out['message-id'] === undefined)
208
+ out['message-id'] = ch.messageId;
209
+ if (typeof ch.inReplyTo === 'string' && out['in-reply-to'] === undefined)
210
+ out['in-reply-to'] = ch.inReplyTo;
211
+ if (typeof ch.references === 'string' && out.references === undefined)
212
+ out.references = ch.references;
213
+ }
214
+ return out;
215
+ };
216
+ /**
217
+ * Parses an SNS notification carrying an SES inbound-email payload into a
218
+ * normalized {@link InboundEmail}.
219
+ *
220
+ * When the SES `Message.content` field is present, it is base64-decoded
221
+ * and parsed as RFC 822 via `mailparser`. When `content` is absent
222
+ * (header-only notifications), we synthesize an `InboundEmail` from the
223
+ * SES `mail` metadata so the caller can still log/dedupe the message.
224
+ *
225
+ * SubscriptionConfirmation messages are returned as a synthetic
226
+ * `InboundEmail` whose `subject` is `'__sns:SubscriptionConfirmation'` and
227
+ * whose `headers['x-sns-subscribe-url']` carries the confirmation URL —
228
+ * applications inspect this so they can subscribe out-of-band.
229
+ *
230
+ * @param _headers - HTTP headers (unused).
231
+ * @param body - Raw HTTP body (SNS JSON).
232
+ * @returns The normalized inbound email.
233
+ */
234
+ export const parseWebhookPayload = async (_headers, body) => {
235
+ let parsed;
236
+ try {
237
+ parsed = parseJsonBody(body);
238
+ }
239
+ catch (error) {
240
+ throw new Error('SES inbound webhook body is not valid JSON.', { cause: error });
241
+ }
242
+ if (!isSnsPayload(parsed)) {
243
+ throw new Error('SES inbound webhook body is not an SNS notification payload.');
244
+ }
245
+ if (parsed.Type === 'SubscriptionConfirmation' || parsed.Type === 'UnsubscribeConfirmation') {
246
+ const headers = {};
247
+ if (typeof parsed.SubscribeURL === 'string')
248
+ headers['x-sns-subscribe-url'] = parsed.SubscribeURL;
249
+ return {
250
+ id: parsed.MessageId,
251
+ from: '',
252
+ to: [],
253
+ subject: `__sns:${parsed.Type}`,
254
+ headers,
255
+ receivedAt: new Date(parsed.Timestamp),
256
+ };
257
+ }
258
+ let sesMessage;
259
+ try {
260
+ sesMessage = JSON.parse(parsed.Message);
261
+ }
262
+ catch (error) {
263
+ throw new Error('SES inbound notification `Message` is not valid JSON.', { cause: error });
264
+ }
265
+ if (!sesMessage || typeof sesMessage !== 'object' || !sesMessage.mail) {
266
+ throw new Error('SES inbound notification is missing the `mail` field.');
267
+ }
268
+ const sesReceivedAt = new Date(sesMessage.mail.timestamp);
269
+ const sesMessageId = sesMessage.mail.messageId;
270
+ if (typeof sesMessage.content === 'string' && sesMessage.content.length > 0) {
271
+ const raw = base64ToBuffer(sesMessage.content);
272
+ const email = await parseRawMimeContent(raw, {
273
+ id: sesMessageId,
274
+ receivedAt: sesReceivedAt,
275
+ });
276
+ if (!email.messageId)
277
+ email.messageId = sesMessageId;
278
+ return email;
279
+ }
280
+ // Header-only notification — synthesize from SES metadata.
281
+ const ch = sesMessage.mail.commonHeaders ?? {};
282
+ const headers = headersFromSesMail(sesMessage.mail);
283
+ const messageIdFromCommon = unwrapMessageId(ch.messageId);
284
+ const inReplyTo = unwrapMessageId(ch.inReplyTo);
285
+ const references = splitReferences(ch.references).map((ref) => unwrapMessageId(ref) ?? ref);
286
+ const email = {
287
+ id: sesMessageId,
288
+ from: Array.isArray(ch.from) && ch.from.length > 0 ? ch.from[0] : (sesMessage.mail.source ?? ''),
289
+ to: Array.isArray(ch.to) && ch.to.length > 0
290
+ ? ch.to.slice()
291
+ : Array.isArray(sesMessage.mail.destination)
292
+ ? sesMessage.mail.destination.slice()
293
+ : [],
294
+ subject: typeof ch.subject === 'string' ? ch.subject : '',
295
+ headers,
296
+ receivedAt: sesReceivedAt,
297
+ };
298
+ if (Array.isArray(ch.cc) && ch.cc.length > 0)
299
+ email.cc = ch.cc.slice();
300
+ if (messageIdFromCommon)
301
+ email.messageId = messageIdFromCommon;
302
+ else
303
+ email.messageId = sesMessageId;
304
+ if (inReplyTo)
305
+ email.inReplyTo = inReplyTo;
306
+ if (references.length > 0)
307
+ email.references = references;
308
+ return email;
309
+ };
310
+ /**
311
+ * Dispatches an outbound reply through the bonded `@molecule/api-emails`
312
+ * transport. The reply's `In-Reply-To` and `References` headers are
313
+ * populated from the original message when present.
314
+ *
315
+ * @param email - The original inbound email being replied to.
316
+ * @param reply - The reply payload.
317
+ * @returns The reply dispatch result.
318
+ */
319
+ export const replyTo = async (email, reply) => {
320
+ const fromAddress = reply.from ?? (email.to.length > 0 ? email.to[0] : undefined);
321
+ if (!fromAddress) {
322
+ throw new Error('Cannot dispatch inbound reply: no `from` address was supplied and the original email has no recipient to fall back to.');
323
+ }
324
+ const subject = reply.subject ?? `Re: ${email.subject}`;
325
+ const threadingHeaders = {};
326
+ if (email.messageId) {
327
+ threadingHeaders['In-Reply-To'] = `<${email.messageId}>`;
328
+ const refs = email.references ? [...email.references] : [];
329
+ refs.push(email.messageId);
330
+ threadingHeaders.References = refs.map((id) => `<${id}>`).join(' ');
331
+ }
332
+ const headers = {
333
+ ...threadingHeaders,
334
+ ...(reply.headers ?? {}),
335
+ };
336
+ const message = {
337
+ from: fromAddress,
338
+ to: email.from,
339
+ subject,
340
+ ...(reply.textBody !== undefined ? { text: reply.textBody } : {}),
341
+ ...(reply.htmlBody !== undefined ? { html: reply.htmlBody } : {}),
342
+ ...(reply.attachments && reply.attachments.length > 0
343
+ ? {
344
+ attachments: reply.attachments.map((a) => ({
345
+ filename: a.name,
346
+ content: base64ToBuffer(a.contentBase64),
347
+ contentType: a.contentType,
348
+ ...(a.contentId !== undefined ? { cid: a.contentId } : {}),
349
+ })),
350
+ }
351
+ : {}),
352
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
353
+ };
354
+ const result = await sendMail(message);
355
+ return {
356
+ id: result.messageId ?? '',
357
+ };
358
+ };
359
+ /**
360
+ * Indicates that this provider supports outbound reply dispatch via
361
+ * {@link replyTo}. The reply path requires the outbound
362
+ * `@molecule/api-emails` bond to be wired with a transport — typically
363
+ * `@molecule/api-emails-ses`.
364
+ *
365
+ * @returns Always `true`.
366
+ */
367
+ export const supportsReply = () => true;
368
+ /**
369
+ * The AWS SES inbound-email provider implementing the
370
+ * {@link InboundEmailProvider} interface.
371
+ */
372
+ export const provider = {
373
+ parseWebhookPayload,
374
+ verifySignature,
375
+ replyTo,
376
+ supportsReply,
377
+ };
378
+ //# 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,16 @@
1
+ /**
2
+ * AWS SES inbound-email secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import type { SecretDefinition } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the AWS SES inbound-email bond. */
15
+ export declare const emailsInboundSesSecretDefinitions: SecretDefinition[];
16
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,qEAAqE;AACrE,eAAO,MAAM,iCAAiC,EAAE,gBAAgB,EAuC/D,CAAA"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * AWS SES inbound-email secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import { registerSecrets } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the AWS SES inbound-email bond. */
15
+ export const emailsInboundSesSecretDefinitions = [
16
+ {
17
+ key: 'AWS_ACCESS_KEY_ID',
18
+ description: 'AWS access key ID — Create an IAM user with the needed policy (SES/S3/SQS) and create an access key under Security credentials.',
19
+ helpUrl: 'https://console.aws.amazon.com/iam/',
20
+ required: true,
21
+ example: 'AKIA...',
22
+ },
23
+ {
24
+ key: 'AWS_SECRET_ACCESS_KEY',
25
+ description: 'AWS secret access key — Shown once when creating the IAM access key — store it immediately.',
26
+ helpUrl: 'https://console.aws.amazon.com/iam/',
27
+ required: true,
28
+ },
29
+ {
30
+ key: 'AWS_SES_REGION',
31
+ description: 'AWS SES region — The AWS region where SES is set up (and out of sandbox for production sending).',
32
+ required: true,
33
+ example: 'us-east-1',
34
+ },
35
+ {
36
+ key: 'AWS_SES_INBOUND_TOPIC_ARN',
37
+ description: 'SES inbound SNS topic ARN — ARN of the SNS topic your SES receipt rule publishes inbound mail to.',
38
+ helpUrl: 'https://console.aws.amazon.com/ses/',
39
+ required: false,
40
+ example: 'arn:aws:sns:us-east-1:123456789012:ses-inbound',
41
+ },
42
+ {
43
+ key: 'AWS_SNS_SIGNING_CERT_HOSTNAME_SUFFIXES',
44
+ description: 'SNS signing-cert hostname allowlist — Comma-separated hostname suffixes allowed for SNS signature certificates; the default (.amazonaws.com) is fine.',
45
+ required: false,
46
+ example: '.amazonaws.com',
47
+ default: '.amazonaws.com',
48
+ },
49
+ ];
50
+ registerSecrets(emailsInboundSesSecretDefinitions);
51
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,qEAAqE;AACrE,MAAM,CAAC,MAAM,iCAAiC,GAAuB;IACnE;QACE,GAAG,EAAE,mBAAmB;QACxB,WAAW,EACT,iIAAiI;QACnI,OAAO,EAAE,qCAAqC;QAC9C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,SAAS;KACnB;IACD;QACE,GAAG,EAAE,uBAAuB;QAC5B,WAAW,EACT,6FAA6F;QAC/F,OAAO,EAAE,qCAAqC;QAC9C,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,gBAAgB;QACrB,WAAW,EACT,kGAAkG;QACpG,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,WAAW;KACrB;IACD;QACE,GAAG,EAAE,2BAA2B;QAChC,WAAW,EACT,mGAAmG;QACrG,OAAO,EAAE,qCAAqC;QAC9C,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,gDAAgD;KAC1D;IACD;QACE,GAAG,EAAE,wCAAwC;QAC7C,WAAW,EACT,uJAAuJ;QACzJ,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,gBAAgB;QACzB,OAAO,EAAE,gBAAgB;KAC1B;CACF,CAAA;AAED,eAAe,CAAC,iCAAiC,CAAC,CAAA"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Type definitions for the AWS SES inbound-emails provider.
3
+ *
4
+ * @module
5
+ */
6
+ export type { InboundEmail, InboundEmailAttachment, InboundEmailProvider, InboundEmailReply, InboundEmailReplyResult, } from '@molecule/api-emails-inbound';
7
+ /**
8
+ * Allowed hostname suffixes for the SNS `SigningCertURL`. AWS SNS only
9
+ * publishes signing certificates from `*.amazonaws.com`; any URL outside
10
+ * this allowlist MUST be rejected to defend against SSRF and certificate
11
+ * substitution attacks.
12
+ *
13
+ * Exposed for unit-testing; not part of the public bond surface.
14
+ */
15
+ export declare const SNS_SIGNING_CERT_HOSTNAME_SUFFIXES: readonly string[];
16
+ /**
17
+ * Shape of an Amazon SNS notification (or SubscriptionConfirmation /
18
+ * UnsubscribeConfirmation) payload, as POSTed to a subscribed HTTPS
19
+ * endpoint. Only the fields used by this bond are typed here.
20
+ *
21
+ * @see https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html
22
+ */
23
+ export interface SnsNotificationPayload {
24
+ /**
25
+ * Discriminates the kind of message: `Notification`,
26
+ * `SubscriptionConfirmation`, or `UnsubscribeConfirmation`.
27
+ */
28
+ Type: string;
29
+ /** A unique UUID for the message. */
30
+ MessageId: string;
31
+ /** The notification topic ARN. */
32
+ TopicArn?: string;
33
+ /**
34
+ * Subject line as supplied by the publisher. Optional for notifications.
35
+ */
36
+ Subject?: string;
37
+ /** Message payload (string). For SES notifications this is JSON. */
38
+ Message: string;
39
+ /** ISO 8601 timestamp when the message was published. */
40
+ Timestamp: string;
41
+ /** Signature version. AWS SNS supports `1` (SHA1) and `2` (SHA256). */
42
+ SignatureVersion: string;
43
+ /** Base64-encoded signature over the canonical string. */
44
+ Signature: string;
45
+ /** URL of the X.509 PEM cert used to sign the message. */
46
+ SigningCertURL: string;
47
+ /** Confirmation token (only on SubscriptionConfirmation messages). */
48
+ Token?: string;
49
+ /** Subscribe URL (only on SubscriptionConfirmation messages). */
50
+ SubscribeURL?: string;
51
+ /** Unsubscribe URL (only on Notification / UnsubscribeConfirmation). */
52
+ UnsubscribeURL?: string;
53
+ }
54
+ /**
55
+ * Shape of the JSON payload SES publishes to SNS for inbound-email
56
+ * notifications. Only the fields used by this bond are typed; SES emits a
57
+ * superset including `verdicts`, `dkim`, etc.
58
+ *
59
+ * @see https://docs.aws.amazon.com/ses/latest/dg/receiving-email-notifications-contents.html
60
+ */
61
+ export interface SesInboundNotificationMessage {
62
+ /** Notification kind. We expect `Received` for inbound mail. */
63
+ notificationType: string;
64
+ /** Metadata about the SES `mail` object. */
65
+ mail: {
66
+ /** ISO timestamp SES received the message. */
67
+ timestamp: string;
68
+ /** Sender as decoded by SES. */
69
+ source: string;
70
+ /** SES-assigned message ID. */
71
+ messageId: string;
72
+ /** Envelope-recipient list. */
73
+ destination: string[];
74
+ /** Common headers SES extracts from the message. */
75
+ commonHeaders?: {
76
+ from?: string[];
77
+ to?: string[];
78
+ cc?: string[];
79
+ bcc?: string[];
80
+ subject?: string;
81
+ messageId?: string;
82
+ inReplyTo?: string;
83
+ references?: string;
84
+ };
85
+ /**
86
+ * All raw headers SES extracted, when `headersTruncated` is `false`.
87
+ */
88
+ headers?: Array<{
89
+ name: string;
90
+ value: string;
91
+ }>;
92
+ };
93
+ /**
94
+ * Raw RFC 822 message content, base64-encoded, present when the SES
95
+ * receipt rule includes the message content. Absent for header-only
96
+ * notifications.
97
+ */
98
+ content?: string;
99
+ }
100
+ declare global {
101
+ namespace NodeJS {
102
+ /**
103
+ * Process Env interface — AWS SES Inbound shares credentials with the
104
+ * outbound `@molecule/api-emails-ses` bond. The SNS signing certificate
105
+ * is fetched at verification time; no signing key is held locally.
106
+ */
107
+ interface ProcessEnv {
108
+ /**
109
+ * Allowlist of hostname suffixes (comma-separated) accepted for the
110
+ * SNS `SigningCertURL`. Defaults to `.amazonaws.com` when unset.
111
+ */
112
+ AWS_SNS_SIGNING_CERT_HOSTNAME_SUFFIXES?: string;
113
+ /**
114
+ * Optional explicit SNS topic ARN. When set, notifications whose
115
+ * `TopicArn` does not match are rejected.
116
+ */
117
+ AWS_SES_INBOUND_TOPIC_ARN?: string;
118
+ }
119
+ }
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,8BAA8B,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,kCAAkC,EAAE,SAAS,MAAM,EAAuB,CAAA;AAEvF;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAA;IAEjB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAA;IAEf,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IAEjB,uEAAuE;IACvE,gBAAgB,EAAE,MAAM,CAAA;IAExB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IAEjB,0DAA0D;IAC1D,cAAc,EAAE,MAAM,CAAA;IAEtB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC5C,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAA;IAExB,4CAA4C;IAC5C,IAAI,EAAE;QACJ,8CAA8C;QAC9C,SAAS,EAAE,MAAM,CAAA;QACjB,gCAAgC;QAChC,MAAM,EAAE,MAAM,CAAA;QACd,+BAA+B;QAC/B,SAAS,EAAE,MAAM,CAAA;QACjB,+BAA+B;QAC/B,WAAW,EAAE,MAAM,EAAE,CAAA;QACrB,oDAAoD;QACpD,aAAa,CAAC,EAAE;YACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;YACf,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;YACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;YACb,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;YACd,OAAO,CAAC,EAAE,MAAM,CAAA;YAChB,SAAS,CAAC,EAAE,MAAM,CAAA;YAClB,SAAS,CAAC,EAAE,MAAM,CAAA;YAClB,UAAU,CAAC,EAAE,MAAM,CAAA;SACpB,CAAA;QACD;;WAEG;QACH,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KACjD,CAAA;IAED;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,MAAM,CAAC;QACf;;;;WAIG;QACH,UAAiB,UAAU;YACzB;;;eAGG;YACH,sCAAsC,CAAC,EAAE,MAAM,CAAA;YAE/C;;;eAGG;YACH,yBAAyB,CAAC,EAAE,MAAM,CAAA;SACnC;KACF;CACF"}
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Type definitions for the AWS SES inbound-emails provider.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Allowed hostname suffixes for the SNS `SigningCertURL`. AWS SNS only
8
+ * publishes signing certificates from `*.amazonaws.com`; any URL outside
9
+ * this allowlist MUST be rejected to defend against SSRF and certificate
10
+ * substitution attacks.
11
+ *
12
+ * Exposed for unit-testing; not part of the public bond surface.
13
+ */
14
+ export const SNS_SIGNING_CERT_HOSTNAME_SUFFIXES = ['.amazonaws.com'];
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAsB,CAAC,gBAAgB,CAAC,CAAA"}