@forumone/throughline-email 0.0.1 → 0.2.1

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 (64) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -41
  4. package/dist/client.d.ts +78 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +63 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/functions/_shared.d.ts +17 -0
  9. package/dist/functions/_shared.d.ts.map +1 -0
  10. package/dist/functions/_shared.js +47 -0
  11. package/dist/functions/_shared.js.map +1 -0
  12. package/dist/functions/index.d.ts +7 -0
  13. package/dist/functions/index.d.ts.map +1 -0
  14. package/dist/functions/index.js +4 -0
  15. package/dist/functions/index.js.map +1 -0
  16. package/dist/functions/notify-approval-decision.d.ts +23 -0
  17. package/dist/functions/notify-approval-decision.d.ts.map +1 -0
  18. package/dist/functions/notify-approval-decision.js +80 -0
  19. package/dist/functions/notify-approval-decision.js.map +1 -0
  20. package/dist/functions/notify-approval-expired.d.ts +21 -0
  21. package/dist/functions/notify-approval-expired.d.ts.map +1 -0
  22. package/dist/functions/notify-approval-expired.js +55 -0
  23. package/dist/functions/notify-approval-expired.js.map +1 -0
  24. package/dist/functions/notify-approval-request.d.ts +24 -0
  25. package/dist/functions/notify-approval-request.d.ts.map +1 -0
  26. package/dist/functions/notify-approval-request.js +93 -0
  27. package/dist/functions/notify-approval-request.js.map +1 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +7 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/options.d.ts +79 -0
  33. package/dist/options.d.ts.map +1 -0
  34. package/dist/options.js +39 -0
  35. package/dist/options.js.map +1 -0
  36. package/dist/plugin.d.ts +18 -0
  37. package/dist/plugin.d.ts.map +1 -0
  38. package/dist/plugin.js +76 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/dist/templates/ApprovalDecision.d.ts +15 -0
  41. package/dist/templates/ApprovalDecision.d.ts.map +1 -0
  42. package/dist/templates/ApprovalDecision.js +56 -0
  43. package/dist/templates/ApprovalDecision.js.map +1 -0
  44. package/dist/templates/ApprovalExpired.d.ts +10 -0
  45. package/dist/templates/ApprovalExpired.d.ts.map +1 -0
  46. package/dist/templates/ApprovalExpired.js +8 -0
  47. package/dist/templates/ApprovalExpired.js.map +1 -0
  48. package/dist/templates/ApprovalRequest.d.ts +24 -0
  49. package/dist/templates/ApprovalRequest.d.ts.map +1 -0
  50. package/dist/templates/ApprovalRequest.js +58 -0
  51. package/dist/templates/ApprovalRequest.js.map +1 -0
  52. package/dist/templates/_layout.d.ts +17 -0
  53. package/dist/templates/_layout.d.ts.map +1 -0
  54. package/dist/templates/_layout.js +39 -0
  55. package/dist/templates/_layout.js.map +1 -0
  56. package/dist/templates/index.d.ts +9 -0
  57. package/dist/templates/index.d.ts.map +1 -0
  58. package/dist/templates/index.js +5 -0
  59. package/dist/templates/index.js.map +1 -0
  60. package/dist/tokens.d.ts +39 -0
  61. package/dist/tokens.d.ts.map +1 -0
  62. package/dist/tokens.js +17 -0
  63. package/dist/tokens.js.map +1 -0
  64. package/package.json +74 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # @forumone/throughline-email
2
+
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [a4b5108]
8
+ - @forumone/throughline-core@0.2.1
9
+
10
+ ## 0.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - fab82fc: Initial release of the email package. Pairs Resend with React Email templates and ships the three notification Inngest functions that close the approval workflow loop. Templates: `ApprovalRequestEmail` (preview + Approve / Request changes / Discuss), `ApprovalDecisionEmail` (granted / declined / changes-requested variants with decision-aware next-step copy), `ApprovalExpiredEmail`. All render to HTML and plaintext from the same React tree. Functions: `createNotifyApprovalRequestFunction` (subscribes to `notification/send-approval-request`, sends per approver in `notifiedApprovers` with each in its own `step.run`), `createNotifyApprovalDecisionFunction` (subscribes to `notification/send-approval-decision`), `createNotifyApprovalExpiredFunction` (subscribes to `approval/expired`). Themed via `EmailBrandTokens` (neutral defaults; brand name lands in header / From name / footer for consistency). Plugin exposes the client and functions via Symbols (`getEmailClient`, `getEmailFunctions`) for the client app's Inngest endpoint to compose. Throws at init if `RESEND_API_KEY` / `EMAIL_FROM_ADDRESS` / `resolveApprover` / `resolveRequester` / `buildActionUrl` are missing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,137 @@
1
1
  # @forumone/throughline-email
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Transactional email for the Throughline framework. Pairs Resend with React Email templates that read brand tokens from the plugin options, and ships three Inngest functions that subscribe to the notification events the audit-event-echo workflow fires.
4
+
5
+ After this package, the approval workflow is end-to-end: a marketer requests approval via Claude, approvers receive a well-designed email with a one-sentence summary, a preview link, and three clear actions, and decisions / expirations route back to the requester automatically.
6
+
7
+ ## What this package provides
8
+
9
+ - **`emailPlugin`** — registers an email client and the three notification Inngest functions on the Payload instance via Symbols.
10
+ - **`createEmailClient`** — Resend wrapper that lazy-imports both `resend` and `@react-email/render` and produces both HTML and plaintext from the same React tree on every send.
11
+ - **Three React Email templates** — `ApprovalRequestEmail`, `ApprovalDecisionEmail` (granted / declined / changes-requested variants), `ApprovalExpiredEmail`. Themed via `EmailBrandTokens`.
12
+ - **Three notification functions** — `createNotifyApprovalRequestFunction`, `createNotifyApprovalDecisionFunction`, `createNotifyApprovalExpiredFunction`.
13
+ - **Brand tokens** — neutral defaults (black on white, system sans, "Your Site"); deployments override via `emailPlugin({ tokens })`.
14
+
15
+ ## Installation
4
16
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
17
+ ```bash
18
+ pnpm add @forumone/throughline-email
19
+ ```
6
20
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-email`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
21
+ Peers: `payload@^3.0.0`, `inngest@^4.0.0`, `react@^18 || ^19`. The plugin lazy-loads `resend` and `@react-email/render` on first send, so a misconfigured deploy fails on first email rather than at boot.
22
+
23
+ ## Usage
24
+
25
+ In your Payload config:
26
+
27
+ ```ts
28
+ import { buildConfig } from 'payload'
29
+ import { auditPlugin, createInngestClient } from '@forumone/throughline-core'
30
+ import { emailPlugin } from '@forumone/throughline-email'
31
+
32
+ const inngest = createInngestClient({ id: 'my-site' })
33
+
34
+ export default buildConfig({
35
+ // collections, db, secret...
36
+ plugins: [
37
+ auditPlugin({ inngest }),
38
+ emailPlugin({
39
+ inngest,
40
+ tokens: { brandName: 'Acme Foundation', brandPrimary: '#5B21B6' },
41
+ resolveApprover: async (userId) => {
42
+ const user = await payload.findByID({ collection: 'users', id: userId })
43
+ return user ? { email: user.email, name: user.name } : null
44
+ },
45
+ resolveRequester: async (userId) => {
46
+ const user = await payload.findByID({ collection: 'users', id: userId })
47
+ return user ? { email: user.email, name: user.name } : null
48
+ },
49
+ buildActionUrl: async ({ approvalId, action, approverId }) => {
50
+ // Wrap your approvals plugin's HMAC token. The action endpoint
51
+ // confirms-on-first-hit, so any well-formed URL works here.
52
+ return `${process.env.NEXT_PUBLIC_SERVER_URL}/api/approvals/action?token=...`
53
+ },
54
+ // Optional preview URL builder for cases where the approval record
55
+ // doesn't carry a previewUrl directly.
56
+ buildPreviewUrl: async ({ approvalId }) =>
57
+ `${process.env.NEXT_PUBLIC_SERVER_URL}/preview?approval=${approvalId}`,
58
+ }),
59
+ ],
60
+ })
61
+ ```
62
+
63
+ Then in your client app's Inngest endpoint:
64
+
65
+ ```ts
66
+ // src/app/api/inngest/route.ts
67
+ import { serve } from 'inngest/next'
68
+ import { getPayload } from 'payload'
69
+ import config from '@payload-config'
70
+ import { inngest } from '@/lib/inngest'
71
+ import { getEmailFunctions } from '@forumone/throughline-email'
72
+
73
+ const payload = await getPayload({ config })
74
+ const emailFunctions = getEmailFunctions(payload)
75
+
76
+ export const { GET, POST, PUT } = serve({
77
+ client: inngest,
78
+ functions: [...emailFunctions /* plus your other functions */],
79
+ })
80
+ ```
81
+
82
+ The plugin registers functions into the Payload instance via Symbol; the endpoint reads them via `getEmailFunctions`. Same shape used by `@forumone/throughline-integrations`.
83
+
84
+ ## How the events flow
85
+
86
+ 1. Approvals server fires `approval.requested` / `approval.granted` / `approval.declined` / `approval.changes_requested` audit events.
87
+ 2. C10's `audit-event-echo` re-fires these as `notification/send-approval-request` and `notification/send-approval-decision`.
88
+ 3. This package's notification functions handle those events and email the right people.
89
+
90
+ The `approval/expired` event (fired by C10's `expire-stale-approvals` cron) is handled directly — no audit-echo translation is needed.
91
+
92
+ ## Brand tokens
93
+
94
+ Tokens are merged onto neutral defaults; pass only what you want to override:
95
+
96
+ ```ts
97
+ emailPlugin({
98
+ tokens: {
99
+ brandName: 'Acme Foundation', // header + From name + footer
100
+ brandPrimary: '#5B21B6', // approve button + discuss link
101
+ fontFamilySans: '"Roobert", system-ui, sans-serif',
102
+ },
103
+ // ...
104
+ })
105
+ ```
106
+
107
+ `brandName` lands in three places (layout header, From display name, footer disclaimer) so "this came from Acme Foundation" stays consistent.
108
+
109
+ ## Why per-recipient `step.run`
110
+
111
+ The approval-request notifier sends to every approver in `notifiedApprovers`. Each recipient is wrapped in its own `step.run`, so a bouncing inbox retries without re-sending to the others. Inngest's idempotency keys are per-step, not per-function — batching all sends in one step would mean every retry re-emails everyone.
112
+
113
+ ## Plaintext is non-optional
114
+
115
+ Every email renders to both HTML and plaintext from the same React tree (React Email's `render(..., { plainText: true })`). Plaintext is required for accessibility (screen readers), deliverability (spam scores improve dramatically), and for clients that refuse HTML.
116
+
117
+ ## Options reference
118
+
119
+ | Option | Default | Notes |
120
+ |---|---|---|
121
+ | `inngest` | required | Fires no events itself; needed to register the three functions |
122
+ | `apiKey` | `RESEND_API_KEY` env | Throws at init if neither is set |
123
+ | `fromAddress` | `EMAIL_FROM_ADDRESS` env | Required |
124
+ | `fromName` | `EMAIL_FROM_NAME` env → `tokens.brandName` → `'Your Site'` | |
125
+ | `replyTo` | `EMAIL_REPLY_TO` env | Optional |
126
+ | `tokens` | `defaultTokens` (merged onto) | Partial override |
127
+ | `approvalsCollectionSlug` | `'approvals'` | Match what your approvals plugin uses |
128
+ | `resolveApprover` | required | `(userId) => {email, name}` |
129
+ | `resolveRequester` | required | `(userId) => {email, name}` |
130
+ | `buildActionUrl` | required | Per-action URL builder; wrap your approvals plugin's HMAC token |
131
+ | `buildPreviewUrl` | optional | Used only when the approval record has no `previewUrl` |
132
+
133
+ ## Related packages
134
+
135
+ - `@forumone/throughline-core` — required peer; provides logger and audit infrastructure
136
+ - `@forumone/throughline-approvals` — owns the approvals collection this package reads
137
+ - `@forumone/throughline-workflows` — `audit-event-echo` is the upstream fan-out; `expire-stale-approvals` fires the expiration event this package subscribes to
@@ -0,0 +1,78 @@
1
+ import type { ReactElement } from 'react';
2
+ export interface EmailClient {
3
+ send(params: SendEmailParams): Promise<SendEmailResult>;
4
+ }
5
+ export interface SendEmailParams {
6
+ to: string | string[];
7
+ subject: string;
8
+ template: ReactElement;
9
+ tags?: Array<{
10
+ name: string;
11
+ value: string;
12
+ }>;
13
+ replyTo?: string;
14
+ }
15
+ export interface SendEmailResult {
16
+ id: string;
17
+ deliveredAt: string;
18
+ }
19
+ export interface EmailClientOptions {
20
+ apiKey: string;
21
+ fromAddress: string;
22
+ fromName: string;
23
+ defaultReplyTo?: string;
24
+ /**
25
+ * Optional Resend constructor override for tests or self-hosted Resend
26
+ * deployments. The default loads `resend` lazily so the package is
27
+ * still importable in environments without Resend installed (e.g. tests
28
+ * that mock the client entirely).
29
+ */
30
+ resendClient?: {
31
+ emails: {
32
+ send: (input: ResendSendInput) => Promise<ResendSendResult>;
33
+ };
34
+ };
35
+ /**
36
+ * Optional renderer override. The default lazily imports
37
+ * `@react-email/render` and produces both HTML and plaintext.
38
+ */
39
+ render?: TemplateRenderer;
40
+ }
41
+ export interface TemplateRenderer {
42
+ toHtml: (template: ReactElement) => Promise<string>;
43
+ toText: (template: ReactElement) => Promise<string>;
44
+ }
45
+ interface ResendSendInput {
46
+ from: string;
47
+ to: string[];
48
+ subject: string;
49
+ html: string;
50
+ text: string;
51
+ tags?: Array<{
52
+ name: string;
53
+ value: string;
54
+ }>;
55
+ replyTo?: string;
56
+ }
57
+ interface ResendSendResult {
58
+ data: {
59
+ id: string;
60
+ } | null;
61
+ error: {
62
+ message: string;
63
+ name?: string;
64
+ } | null;
65
+ }
66
+ /**
67
+ * Wraps Resend with React Email rendering and a stable result envelope.
68
+ * Each `send` call produces both HTML and plaintext from the same React
69
+ * tree — plaintext is non-optional for accessibility (screen readers),
70
+ * deliverability (spam scores), and clients that refuse HTML.
71
+ *
72
+ * Construction is lazy: the Resend SDK and the renderer are only loaded
73
+ * when `send()` is first called. That keeps tests that mock the client
74
+ * fully runnable in environments where neither package is installed.
75
+ */
76
+ export declare function createEmailClient(options: EmailClientOptions): EmailClient;
77
+ export {};
78
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAEzC,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,YAAY,CAAA;IACtB,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QAAE,MAAM,EAAE;YAAE,IAAI,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;SAAE,CAAA;KAAE,CAAA;IAC1F;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACpD;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,EAAE,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACjD;AAQD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAuD1E"}
package/dist/client.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Wraps Resend with React Email rendering and a stable result envelope.
3
+ * Each `send` call produces both HTML and plaintext from the same React
4
+ * tree — plaintext is non-optional for accessibility (screen readers),
5
+ * deliverability (spam scores), and clients that refuse HTML.
6
+ *
7
+ * Construction is lazy: the Resend SDK and the renderer are only loaded
8
+ * when `send()` is first called. That keeps tests that mock the client
9
+ * fully runnable in environments where neither package is installed.
10
+ */
11
+ export function createEmailClient(options) {
12
+ const from = `${options.fromName} <${options.fromAddress}>`;
13
+ let cachedResend = options.resendClient;
14
+ let cachedRenderer = options.render;
15
+ async function getResend() {
16
+ if (cachedResend)
17
+ return cachedResend;
18
+ const mod = (await import('resend'));
19
+ cachedResend = new mod.Resend(options.apiKey);
20
+ return cachedResend;
21
+ }
22
+ async function getRenderer() {
23
+ if (cachedRenderer)
24
+ return cachedRenderer;
25
+ const mod = (await import('@react-email/render'));
26
+ cachedRenderer = {
27
+ toHtml: (template) => mod.render(template),
28
+ toText: (template) => mod.render(template, { plainText: true }),
29
+ };
30
+ return cachedRenderer;
31
+ }
32
+ return {
33
+ async send(params) {
34
+ const renderer = await getRenderer();
35
+ const [html, text] = await Promise.all([
36
+ renderer.toHtml(params.template),
37
+ renderer.toText(params.template),
38
+ ]);
39
+ const resend = await getResend();
40
+ const input = {
41
+ from,
42
+ to: Array.isArray(params.to) ? params.to : [params.to],
43
+ subject: params.subject,
44
+ html,
45
+ text,
46
+ };
47
+ if (params.tags)
48
+ input.tags = params.tags;
49
+ const replyTo = params.replyTo ?? options.defaultReplyTo;
50
+ if (replyTo)
51
+ input.replyTo = replyTo;
52
+ const result = await resend.emails.send(input);
53
+ if (result.error) {
54
+ throw new Error(`Email send failed: ${result.error.message}`);
55
+ }
56
+ return {
57
+ id: result.data?.id ?? 'unknown',
58
+ deliveredAt: new Date().toISOString(),
59
+ };
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAgEA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,CAAA;IAE3D,IAAI,YAAY,GAAmD,OAAO,CAAC,YAAY,CAAA;IACvF,IAAI,cAAc,GAAiC,OAAO,CAAC,MAAM,CAAA;IAEjE,KAAK,UAAU,SAAS;QACtB,IAAI,YAAY;YAAE,OAAO,YAAY,CAAA;QACrC,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAA2B,CAAA;QAC9D,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAC7C,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,KAAK,UAAU,WAAW;QACxB,IAAI,cAAc;YAAE,OAAO,cAAc,CAAA;QACzC,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAE/C,CAAA;QACD,cAAc,GAAG;YACf,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC1C,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SAChE,CAAA;QACD,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,MAAM;YACf,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAA;YACpC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACrC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;aACjC,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAA;YAChC,MAAM,KAAK,GAAoB;gBAC7B,IAAI;gBACJ,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtD,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI;gBACJ,IAAI;aACL,CAAA;YACD,IAAI,MAAM,CAAC,IAAI;gBAAE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;YACzC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,cAAc,CAAA;YACxD,IAAI,OAAO;gBAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;YAEpC,MAAM,MAAM,GAAG,MAAM,MAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC/C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;YAC/D,CAAC;YACD,OAAO;gBACL,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,SAAS;gBAChC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACtC,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ApprovalTargetKind } from '../templates/index.js';
2
+ /**
3
+ * Maps a collection slug to the human-friendly noun the templates render.
4
+ * Defaults to "item" so unknown collections still produce sensible copy
5
+ * ("the item Homepage" reads weirder than "the page Homepage" but it's
6
+ * better than dropping the noun entirely).
7
+ */
8
+ export declare function targetKindFromCollection(slug: string): ApprovalTargetKind;
9
+ export declare function unwrapRelationshipId(value: unknown): string | null;
10
+ /**
11
+ * Defensive normalizer for the `notifiedApprovers` json field. Older
12
+ * snapshots stored an array of strings; the post-C7 fixup wraps each
13
+ * as `{ id, at, channel }`. We accept either shape.
14
+ */
15
+ export declare function readApproverIds(value: unknown): string[];
16
+ export declare function formatHumanDate(iso: string): string;
17
+ //# sourceMappingURL=_shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/functions/_shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAO/D;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAEzE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAMlE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAWxD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAInD"}
@@ -0,0 +1,47 @@
1
+ const COLLECTION_TO_KIND = {
2
+ pages: 'page',
3
+ posts: 'post',
4
+ };
5
+ /**
6
+ * Maps a collection slug to the human-friendly noun the templates render.
7
+ * Defaults to "item" so unknown collections still produce sensible copy
8
+ * ("the item Homepage" reads weirder than "the page Homepage" but it's
9
+ * better than dropping the noun entirely).
10
+ */
11
+ export function targetKindFromCollection(slug) {
12
+ return COLLECTION_TO_KIND[slug] ?? 'item';
13
+ }
14
+ export function unwrapRelationshipId(value) {
15
+ if (typeof value === 'string')
16
+ return value;
17
+ if (value && typeof value === 'object' && 'id' in value) {
18
+ return String(value.id);
19
+ }
20
+ return null;
21
+ }
22
+ /**
23
+ * Defensive normalizer for the `notifiedApprovers` json field. Older
24
+ * snapshots stored an array of strings; the post-C7 fixup wraps each
25
+ * as `{ id, at, channel }`. We accept either shape.
26
+ */
27
+ export function readApproverIds(value) {
28
+ if (!Array.isArray(value))
29
+ return [];
30
+ const ids = [];
31
+ for (const entry of value) {
32
+ if (typeof entry === 'string') {
33
+ ids.push(entry);
34
+ }
35
+ else if (entry && typeof entry === 'object' && 'id' in entry) {
36
+ ids.push(String(entry.id));
37
+ }
38
+ }
39
+ return ids;
40
+ }
41
+ export function formatHumanDate(iso) {
42
+ const date = new Date(iso);
43
+ if (Number.isNaN(date.getTime()))
44
+ return iso;
45
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
46
+ }
47
+ //# sourceMappingURL=_shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_shared.js","sourceRoot":"","sources":["../../src/functions/_shared.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAuC;IAC7D,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,MAAM;CACd,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,MAAM,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;QACxD,OAAO,MAAM,CAAE,KAAyB,CAAC,EAAE,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC;aAAM,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;YAC/D,GAAG,CAAC,IAAI,CAAC,MAAM,CAAE,KAAyB,CAAC,EAAE,CAAC,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,GAAG,CAAA;IAC5C,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;AAC9F,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { createNotifyApprovalRequestFunction } from './notify-approval-request.js';
2
+ export type { NotifyApprovalRequestDeps } from './notify-approval-request.js';
3
+ export { createNotifyApprovalDecisionFunction } from './notify-approval-decision.js';
4
+ export type { NotifyApprovalDecisionDeps } from './notify-approval-decision.js';
5
+ export { createNotifyApprovalExpiredFunction } from './notify-approval-expired.js';
6
+ export type { NotifyApprovalExpiredDeps } from './notify-approval-expired.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAA;AAClF,YAAY,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,EAAE,oCAAoC,EAAE,MAAM,+BAA+B,CAAA;AACpF,YAAY,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAA;AAC/E,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAA;AAClF,YAAY,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { createNotifyApprovalRequestFunction } from './notify-approval-request.js';
2
+ export { createNotifyApprovalDecisionFunction } from './notify-approval-decision.js';
3
+ export { createNotifyApprovalExpiredFunction } from './notify-approval-expired.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAA;AAElF,OAAO,EAAE,oCAAoC,EAAE,MAAM,+BAA+B,CAAA;AAEpF,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,23 @@
1
+ import type { Inngest, InngestFunction } from 'inngest';
2
+ import type { Payload } from 'payload';
3
+ import type { EmailClient } from '../client.js';
4
+ import type { EmailBrandTokens } from '../tokens.js';
5
+ import { type EmailPluginOptions } from '../options.js';
6
+ export interface NotifyApprovalDecisionDeps {
7
+ inngest: Inngest;
8
+ payload: Payload;
9
+ client: EmailClient;
10
+ tokens: EmailBrandTokens;
11
+ options: EmailPluginOptions;
12
+ /** Override the function id. Default: `notify-approval-decision`. */
13
+ id?: string;
14
+ }
15
+ /**
16
+ * Subscribes to `notification/send-approval-decision` (fired by the C10
17
+ * audit-event-echo when one of `approval.granted`, `approval.declined`,
18
+ * or `approval.changes_requested` is recorded). The decider's audit
19
+ * action travels with the event so we render the right variant without
20
+ * re-checking the approval's status.
21
+ */
22
+ export declare function createNotifyApprovalDecisionFunction(deps: NotifyApprovalDecisionDeps): InngestFunction.Any;
23
+ //# sourceMappingURL=notify-approval-decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-decision.d.ts","sourceRoot":"","sources":["../../src/functions/notify-approval-decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAKpD,OAAO,EAAqC,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAG1F,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,kBAAkB,CAAA;IAC3B,qEAAqE;IACrE,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAcD;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CAClD,IAAI,EAAE,0BAA0B,GAC/B,eAAe,CAAC,GAAG,CAyErB"}
@@ -0,0 +1,80 @@
1
+ import { ApprovalDecisionEmail, } from '../templates/index.js';
2
+ import { DEFAULT_APPROVALS_COLLECTION_SLUG } from '../options.js';
3
+ import { unwrapRelationshipId } from './_shared.js';
4
+ const DECISION_BY_ACTION = {
5
+ 'approval.granted': 'granted',
6
+ 'approval.declined': 'declined',
7
+ 'approval.changes_requested': 'changes-requested',
8
+ };
9
+ const SUBJECT_BY_DECISION = {
10
+ granted: (title) => `Approved: ${title}`,
11
+ declined: (title) => `Not approved: ${title}`,
12
+ 'changes-requested': (title) => `Changes requested on ${title}`,
13
+ };
14
+ /**
15
+ * Subscribes to `notification/send-approval-decision` (fired by the C10
16
+ * audit-event-echo when one of `approval.granted`, `approval.declined`,
17
+ * or `approval.changes_requested` is recorded). The decider's audit
18
+ * action travels with the event so we render the right variant without
19
+ * re-checking the approval's status.
20
+ */
21
+ export function createNotifyApprovalDecisionFunction(deps) {
22
+ return deps.inngest.createFunction({
23
+ id: deps.id ?? 'notify-approval-decision',
24
+ retries: 3,
25
+ triggers: [{ event: 'notification/send-approval-decision' }],
26
+ }, async ({ event, step, logger }) => {
27
+ const data = (event.data ?? {});
28
+ const approvalId = data.approvalId;
29
+ const decisionAction = data.decision ?? '';
30
+ if (!approvalId)
31
+ return { skipped: true, reason: 'no-approvalId' };
32
+ const decision = DECISION_BY_ACTION[decisionAction];
33
+ if (!decision) {
34
+ logger.warn('Unknown decision action; skipping', { approvalId, decisionAction });
35
+ return { skipped: true, reason: 'unknown-decision' };
36
+ }
37
+ const collectionSlug = deps.options.approvalsCollectionSlug ?? DEFAULT_APPROVALS_COLLECTION_SLUG;
38
+ const approval = await step.run('load-approval', async () => deps.payload.findByID({ collection: collectionSlug, id: approvalId, depth: 1 }));
39
+ if (!approval)
40
+ return { skipped: true, reason: 'approval-not-found' };
41
+ const requesterId = unwrapRelationshipId(approval['requestedBy']);
42
+ if (!requesterId) {
43
+ logger.warn('Approval has no requester; cannot notify', { approvalId });
44
+ return { skipped: true, reason: 'no-requester' };
45
+ }
46
+ const targetTitle = String(approval['targetTitle'] ?? '(untitled)');
47
+ const decisionNotes = typeof approval['decisionNotes'] === 'string' ? approval['decisionNotes'] : undefined;
48
+ const previewUrl = typeof approval['previewUrl'] === 'string' ? approval['previewUrl'] : '';
49
+ const decidedById = unwrapRelationshipId(approval['decidedBy']);
50
+ const decidedByRecord = decidedById ? await deps.options.resolveApprover(decidedById) : null;
51
+ const decidedByName = decidedByRecord?.name ?? decidedByRecord?.email ?? 'a reviewer';
52
+ await step.run(`send-to-${requesterId}`, async () => {
53
+ const requester = await deps.options.resolveRequester(requesterId);
54
+ if (!requester) {
55
+ logger.warn('No email for requester; skipping', { requesterId, approvalId });
56
+ return;
57
+ }
58
+ await deps.client.send({
59
+ to: requester.email,
60
+ subject: SUBJECT_BY_DECISION[decision](targetTitle),
61
+ template: ApprovalDecisionEmail({
62
+ requesterName: requester.name ?? requester.email,
63
+ decidedBy: decidedByName,
64
+ targetTitle,
65
+ decision,
66
+ ...(decisionNotes ? { decisionNotes } : {}),
67
+ previewUrl,
68
+ tokens: deps.tokens,
69
+ }),
70
+ tags: [
71
+ { name: 'type', value: 'approval-decision' },
72
+ { name: 'approval-id', value: approvalId },
73
+ { name: 'decision', value: decision },
74
+ ],
75
+ });
76
+ });
77
+ return { approvalId, decision, sent: 1 };
78
+ });
79
+ }
80
+ //# sourceMappingURL=notify-approval-decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-decision.js","sourceRoot":"","sources":["../../src/functions/notify-approval-decision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Inngest, InngestFunction } from 'inngest';
2
+ import type { Payload } from 'payload';
3
+ import type { EmailClient } from '../client.js';
4
+ import type { EmailBrandTokens } from '../tokens.js';
5
+ import { type EmailPluginOptions } from '../options.js';
6
+ export interface NotifyApprovalExpiredDeps {
7
+ inngest: Inngest;
8
+ payload: Payload;
9
+ client: EmailClient;
10
+ tokens: EmailBrandTokens;
11
+ options: EmailPluginOptions;
12
+ /** Override the function id. Default: `notify-approval-expired`. */
13
+ id?: string;
14
+ }
15
+ /**
16
+ * Subscribes to `approval/expired` (fired by the C10 expire-stale-approvals
17
+ * cron). Notifies the original requester so they can decide whether to
18
+ * re-request approval or drop the change.
19
+ */
20
+ export declare function createNotifyApprovalExpiredFunction(deps: NotifyApprovalExpiredDeps): InngestFunction.Any;
21
+ //# sourceMappingURL=notify-approval-expired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-expired.d.ts","sourceRoot":"","sources":["../../src/functions/notify-approval-expired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,OAAO,EAAqC,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAG1F,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,kBAAkB,CAAA;IAC3B,oEAAoE;IACpE,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,yBAAyB,GAC9B,eAAe,CAAC,GAAG,CA0DrB"}