@aglyn/shared-util-email 1.0.0-beta.218 → 1.0.0-beta.220

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.
Files changed (53) hide show
  1. package/README.md +58 -19
  2. package/package.json +3 -3
  3. package/src/index.d.ts +2 -0
  4. package/src/index.js +9 -1
  5. package/src/index.js.map +1 -1
  6. package/src/lib/email-delivery-events.d.ts +41 -48
  7. package/src/lib/email-delivery-events.js +18 -247
  8. package/src/lib/email-delivery-events.js.map +1 -1
  9. package/src/lib/email-health.d.ts +30 -43
  10. package/src/lib/email-health.js +45 -141
  11. package/src/lib/email-health.js.map +1 -1
  12. package/src/lib/hosted-page-screen.d.ts +27 -0
  13. package/src/lib/hosted-page-screen.js +228 -23
  14. package/src/lib/hosted-page-screen.js.map +1 -1
  15. package/src/lib/mail-provider-resend.d.ts +194 -0
  16. package/src/lib/mail-provider-resend.js +649 -0
  17. package/src/lib/mail-provider-resend.js.map +1 -0
  18. package/src/lib/mail-provider-webhook.d.ts +80 -0
  19. package/src/lib/mail-provider-webhook.js +156 -0
  20. package/src/lib/mail-provider-webhook.js.map +1 -0
  21. package/src/lib/mail-provider.d.ts +148 -0
  22. package/src/lib/mail-provider.js +84 -0
  23. package/src/lib/mail-provider.js.map +1 -0
  24. package/src/lib/mail-providers.d.ts +99 -0
  25. package/src/lib/mail-providers.js +182 -0
  26. package/src/lib/mail-providers.js.map +1 -0
  27. package/src/lib/outbound-phishing-screen.d.ts +80 -3
  28. package/src/lib/outbound-phishing-screen.js +457 -29
  29. package/src/lib/outbound-phishing-screen.js.map +1 -1
  30. package/src/lib/received-email.d.ts +7 -44
  31. package/src/lib/received-email.js +8 -104
  32. package/src/lib/received-email.js.map +1 -1
  33. package/src/lib/risk-notice-catalog.d.ts +32 -2
  34. package/src/lib/risk-notice-catalog.js +86 -55
  35. package/src/lib/risk-notice-catalog.js.map +1 -1
  36. package/src/lib/send-ceilings.d.ts +0 -57
  37. package/src/lib/send-ceilings.js +0 -47
  38. package/src/lib/send-ceilings.js.map +1 -1
  39. package/src/lib/send-email.d.ts +52 -58
  40. package/src/lib/send-email.js +128 -134
  41. package/src/lib/send-email.js.map +1 -1
  42. package/src/lib/send-rate.js +4 -3
  43. package/src/lib/send-rate.js.map +1 -1
  44. package/src/lib/sending-domain.d.ts +15 -0
  45. package/src/lib/sending-domain.js.map +1 -1
  46. package/src/lib/system-email-catalog.js +37 -11
  47. package/src/lib/system-email-catalog.js.map +1 -1
  48. package/src/lib/tenant-email-catalog.d.ts +7 -0
  49. package/src/lib/tenant-email-catalog.js +10 -591
  50. package/src/lib/tenant-email-catalog.js.map +1 -1
  51. package/src/lib/tenant-emails.generated.d.ts +11 -0
  52. package/src/lib/tenant-emails.generated.js +649 -0
  53. package/src/lib/tenant-emails.generated.js.map +1 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * RESEND, as one {@link MailProvider} — the only module in the platform that
19
+ * knows Resend's endpoints, payloads, event names and error names.
20
+ *
21
+ * It is the provider a deployment gets when it names none, which is how
22
+ * every deployment that set `RESEND_API_KEY` before the contract existed
23
+ * keeps sending exactly as it did. Two credentials, on purpose:
24
+ *
25
+ * - `RESEND_API_KEY` sends. It is provisioned sending-scoped, so a leaked
26
+ * copy cannot enumerate everyone the platform ever wrote to.
27
+ * - `RESEND_READ_API_KEY` is full-access and only ever reads: history, one
28
+ * message's body, received mail and the account's sending domains. A
29
+ * sending-scoped key answers every such read `401 restricted_api_key`.
30
+ *
31
+ * Pure but for the `fetch` calls, each inside a function that must be called
32
+ * to do anything, so a client component that reaches this module through the
33
+ * library's barrel executes nothing on load.
34
+ */
35
+ import { type EmailDeliveryEvent, type EmailDeliveryHistorySource, type EmailDeliveryMessage, type EmailDeliveryMessageSource, type EmailDeliverySnapshot } from './email-delivery-events';
36
+ import type { EmailCredentialReport } from './email-health';
37
+ import { type MailInboundEvent, type MailProvider, type MailProviderMessage, type MailSendingDomain } from './mail-provider';
38
+ import type { ReceivedEmail, ReceivedEmailSource } from './received-email';
39
+ /** The slug stored on every delivery record this provider produces. */
40
+ export declare const RESEND_PROVIDER_ID = "resend";
41
+ /** The sending key's environment variable. */
42
+ export declare const RESEND_SEND_KEY_SETTING = "RESEND_API_KEY";
43
+ /** The full-access read key's environment variable. */
44
+ export declare const RESEND_READ_KEY_SETTING = "RESEND_READ_API_KEY";
45
+ type Env = Record<string, string | undefined>;
46
+ /** The send endpoint. */
47
+ export declare const RESEND_SEND_ENDPOINT = "https://api.resend.com/emails";
48
+ /** A Resend send payload in the provider's own wire shape. */
49
+ export interface ResendSendPayload {
50
+ to?: unknown;
51
+ from?: unknown;
52
+ subject?: unknown;
53
+ [field: string]: unknown;
54
+ }
55
+ /**
56
+ * The one place that POSTs to Resend's send endpoint, and the last thing
57
+ * standing between a payload and the network.
58
+ *
59
+ * A payload carrying no recipient cannot become a message. Resend answers it
60
+ * `422 missing_required_field`, which costs an API call and then shows up in
61
+ * the vendor dashboard as a red line indistinguishable from mail that
62
+ * genuinely failed to deliver — carrying no subject, no recipient and nothing
63
+ * naming the code that produced it. Diagnosing that means reading a log
64
+ * outside the deployment and guessing. So the refusal happens here, before
65
+ * the fetch, and names the caller's `context`.
66
+ *
67
+ * It throws rather than returning a result: this is a programming error,
68
+ * not a delivery outcome. `sendEmail` filters recipients well before it
69
+ * reaches this call, so nothing on the ordinary path can trip it. The guard
70
+ * exists because `RESEND_SEND_ENDPOINT` is exported and any module can
71
+ * therefore reach the send endpoint on its own, bypassing every check
72
+ * `sendEmail` owns.
73
+ */
74
+ export declare function postResendEmail(apiKey: string, payload: ResendSendPayload, context?: string): Promise<Response>;
75
+ /** One message as Resend's send endpoint takes it. */
76
+ export declare function resendSendPayload(message: MailProviderMessage): ResendSendPayload;
77
+ /**
78
+ * Where the credential probe and the domain read ask their questions.
79
+ *
80
+ * Deliberately NOT the send endpoint. A probe aimed at `/emails` is a send
81
+ * attempt however empty its body is: it consumes an API call, and Resend
82
+ * records it in the account's logs as a `422` on `POST /emails` with no
83
+ * recipient, no subject and nothing identifying the caller — a line an
84
+ * operator reading that dashboard has to treat as failed mail. A domain read
85
+ * cannot create a message and cannot be mistaken for one.
86
+ */
87
+ export declare const RESEND_DOMAINS_ENDPOINT = "https://api.resend.com/domains";
88
+ /**
89
+ * Whether the sending key is accepted — without sending anything to anybody,
90
+ * and without leaving anything behind that reads as failed mail.
91
+ *
92
+ * How: a `GET` of the domains collection. The question is only ever "does
93
+ * Resend recognize this credential", so the probe reads the ERROR NAME rather
94
+ * than the status, because `401` and `403` each carry both meanings:
95
+ *
96
+ * - `2xx` — the key is accepted and has read scope → `ok`
97
+ * - `restricted_api_key` / `invalid_permission` — Resend authenticated the
98
+ * key and then denied it this read. A sending-scoped key always answers
99
+ * this way, and getting the answer proves the credential works → `ok`
100
+ * - `missing_api_key` / `validation_error` / `suspended_api_key` — the key
101
+ * itself was refused → `invalid-key`
102
+ * - anything else → `unknown`
103
+ *
104
+ * An unrecognized rejection is `unknown`, never `invalid-key`: this feeds a
105
+ * staff diagnostics screen whose whole value is that a red line means
106
+ * something, and a shape we have not seen before is not evidence that a
107
+ * working key is broken.
108
+ */
109
+ export declare function checkResendCredentials(apiKey: string): Promise<EmailCredentialReport>;
110
+ /**
111
+ * Every sending domain on the account, with its verification and tracking
112
+ * state. Throws with the status when Resend does not answer 2xx, so a caller
113
+ * never reads "I could not look" as "there is nothing there".
114
+ */
115
+ export declare function readResendSendingDomains(apiKey: string, endpoint?: string): Promise<MailSendingDomain[]>;
116
+ /**
117
+ * Turns one Resend webhook payload into zero or more of our events.
118
+ *
119
+ * One event per recipient, not per message: a send addressed to three people
120
+ * produces one webhook, and a staff view keyed on a person has to be able to
121
+ * find it under each of them.
122
+ *
123
+ * Returns an empty array for anything unrecognised — a contact or domain
124
+ * event, an inbound `email.received`, a type added after this was written.
125
+ * Silence rather than a throw, because a webhook handler that 500s on an
126
+ * unfamiliar event teaches the provider to retry it forever.
127
+ *
128
+ * @param payload The parsed webhook body.
129
+ * @param receivedAtMs Fallback timestamp for a payload that carries none.
130
+ */
131
+ export declare function normalizeResendDeliveryEvents(payload: unknown, receivedAtMs: number): EmailDeliveryEvent[];
132
+ /**
133
+ * Turns one entry from Resend's `GET /emails` list into zero or more
134
+ * snapshots — one per recipient, for the same reason the event adapter fans
135
+ * out: the staff view is keyed on a person.
136
+ *
137
+ * An unrecognised `last_event` falls back to `sent` rather than being
138
+ * dropped. The message demonstrably exists and was addressed to somebody, and
139
+ * "we sent this and cannot characterise what happened next" is a far more
140
+ * useful row than no row — which is the state that sent a staffer to the
141
+ * vendor dashboard in the first place.
142
+ */
143
+ export declare function normalizeResendSentEmails(raw: unknown): EmailDeliverySnapshot[];
144
+ /** The list endpoint. Paginates with `after=<id>`; caps at 100. */
145
+ export declare const RESEND_EMAILS_ENDPOINT = "https://api.resend.com/emails";
146
+ /**
147
+ * {@link EmailDeliveryHistorySource} for Resend. Needs the full-access key.
148
+ *
149
+ * The list endpoint takes `limit`, `after` and `before` and no recipient
150
+ * filter, which is why the source is a cursor-paged sweep.
151
+ */
152
+ export declare function resendDeliveryHistorySource(apiKey: string): EmailDeliveryHistorySource;
153
+ /** Resend's `GET /emails/:id` payload, in our vocabulary. */
154
+ export declare function normalizeResendMessage(raw: unknown): EmailDeliveryMessage | null;
155
+ /** Resend's single-message endpoint. Needs the full-access key. */
156
+ export declare function resendDeliveryMessageSource(apiKey: string): EmailDeliveryMessageSource;
157
+ /** The webhook event type a received message arrives as. */
158
+ export declare const RESEND_RECEIVED_EVENT = "email.received";
159
+ /** `GET {endpoint}/{id}` — one received message with its text and headers. */
160
+ export declare const RESEND_RECEIVING_ENDPOINT = "https://api.resend.com/emails/receiving";
161
+ /**
162
+ * The provider's id of the message an `email.received` event announces,
163
+ * or `null` for any other event — including a malformed one, which is
164
+ * acknowledged and dropped rather than retried forever.
165
+ */
166
+ export declare function resendReceivedEventId(event: unknown): string | null;
167
+ /**
168
+ * Every recipient the event names — To, Cc, Bcc and the addresses the
169
+ * message was received for — so a route can find what it is looking for in
170
+ * the metadata before it spends a read on the body.
171
+ */
172
+ export declare function resendReceivedEventRecipients(event: unknown): string[];
173
+ /**
174
+ * One received message as the receiving API answers it, in the neutral
175
+ * shape. `null` for a payload with no id, which is not a message.
176
+ */
177
+ export declare function normalizeResendReceivedEmail(raw: unknown, nowMs?: number): ReceivedEmail | null;
178
+ /**
179
+ * {@link ReceivedEmailSource} for Resend. Needs the full-access key. A `404`
180
+ * is `null` — the message expired or never existed — and anything else
181
+ * throws with the status, so the caller decides whether the provider should
182
+ * retry.
183
+ */
184
+ export declare function resendReceivedEmailSource(apiKey: string, fetchImpl?: typeof fetch): ReceivedEmailSource;
185
+ /** A received-mail notification, or `null` for any other event. */
186
+ export declare function resendInboundEvent(event: unknown): MailInboundEvent | null;
187
+ /**
188
+ * Resend as a {@link MailProvider}, reading its two keys from `env` at each
189
+ * call — never at module load, because these run in serverless handlers
190
+ * whose module may be evaluated during a build, long before the runtime
191
+ * environment exists.
192
+ */
193
+ export declare function resendMailProvider(env?: Env): MailProvider;
194
+ export {};