@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
@@ -0,0 +1,55 @@
1
+ import { ApprovalExpiredEmail } from '../templates/index.js';
2
+ import { DEFAULT_APPROVALS_COLLECTION_SLUG } from '../options.js';
3
+ import { formatHumanDate, unwrapRelationshipId } from './_shared.js';
4
+ /**
5
+ * Subscribes to `approval/expired` (fired by the C10 expire-stale-approvals
6
+ * cron). Notifies the original requester so they can decide whether to
7
+ * re-request approval or drop the change.
8
+ */
9
+ export function createNotifyApprovalExpiredFunction(deps) {
10
+ return deps.inngest.createFunction({
11
+ id: deps.id ?? 'notify-approval-expired',
12
+ retries: 3,
13
+ triggers: [{ event: 'approval/expired' }],
14
+ }, async ({ event, step, logger }) => {
15
+ const data = (event.data ?? {});
16
+ const approvalId = data.approvalId;
17
+ if (!approvalId)
18
+ return { skipped: true, reason: 'no-approvalId' };
19
+ const collectionSlug = deps.options.approvalsCollectionSlug ?? DEFAULT_APPROVALS_COLLECTION_SLUG;
20
+ const approval = await step.run('load-approval', async () => deps.payload.findByID({ collection: collectionSlug, id: approvalId, depth: 1 }));
21
+ if (!approval)
22
+ return { skipped: true, reason: 'approval-not-found' };
23
+ const requesterId = data.requesterId ?? unwrapRelationshipId(approval['requestedBy']);
24
+ if (!requesterId) {
25
+ logger.warn('Expired approval has no requester; cannot notify', { approvalId });
26
+ return { skipped: true, reason: 'no-requester' };
27
+ }
28
+ const targetTitle = String(approval['targetTitle'] ?? '(untitled)');
29
+ const requestedAtIso = String(approval['requestedAt'] ?? '');
30
+ const requestedAt = requestedAtIso ? formatHumanDate(requestedAtIso) : 'an earlier date';
31
+ await step.run(`send-to-${requesterId}`, async () => {
32
+ const requester = await deps.options.resolveRequester(requesterId);
33
+ if (!requester) {
34
+ logger.warn('No email for requester; skipping', { requesterId, approvalId });
35
+ return;
36
+ }
37
+ await deps.client.send({
38
+ to: requester.email,
39
+ subject: `Approval expired: ${targetTitle}`,
40
+ template: ApprovalExpiredEmail({
41
+ requesterName: requester.name ?? requester.email,
42
+ targetTitle,
43
+ requestedAt,
44
+ tokens: deps.tokens,
45
+ }),
46
+ tags: [
47
+ { name: 'type', value: 'approval-expired' },
48
+ { name: 'approval-id', value: approvalId },
49
+ ],
50
+ });
51
+ });
52
+ return { approvalId, sent: 1 };
53
+ });
54
+ }
55
+ //# sourceMappingURL=notify-approval-expired.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-expired.js","sourceRoot":"","sources":["../../src/functions/notify-approval-expired.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
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 NotifyApprovalRequestDeps {
7
+ inngest: Inngest;
8
+ payload: Payload;
9
+ client: EmailClient;
10
+ tokens: EmailBrandTokens;
11
+ options: EmailPluginOptions;
12
+ /** Override the function id. Default: `notify-approval-request`. */
13
+ id?: string;
14
+ }
15
+ /**
16
+ * Subscribes to `notification/send-approval-request` (fired by the C10
17
+ * audit-event-echo when `approval.requested` is recorded). Loads the
18
+ * approval, looks up the requester and each approver-id stored in
19
+ * `notifiedApprovers`, and sends one email per approver. Each email is
20
+ * its own `step.run` so a bouncing recipient retries without re-sending
21
+ * to the others.
22
+ */
23
+ export declare function createNotifyApprovalRequestFunction(deps: NotifyApprovalRequestDeps): InngestFunction.Any;
24
+ //# sourceMappingURL=notify-approval-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-request.d.ts","sourceRoot":"","sources":["../../src/functions/notify-approval-request.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;AAQ1F,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;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,yBAAyB,GAC9B,eAAe,CAAC,GAAG,CAqGrB"}
@@ -0,0 +1,93 @@
1
+ import { ApprovalRequestEmail } from '../templates/index.js';
2
+ import { DEFAULT_APPROVALS_COLLECTION_SLUG } from '../options.js';
3
+ import { formatHumanDate, readApproverIds, targetKindFromCollection, unwrapRelationshipId, } from './_shared.js';
4
+ /**
5
+ * Subscribes to `notification/send-approval-request` (fired by the C10
6
+ * audit-event-echo when `approval.requested` is recorded). Loads the
7
+ * approval, looks up the requester and each approver-id stored in
8
+ * `notifiedApprovers`, and sends one email per approver. Each email is
9
+ * its own `step.run` so a bouncing recipient retries without re-sending
10
+ * to the others.
11
+ */
12
+ export function createNotifyApprovalRequestFunction(deps) {
13
+ return deps.inngest.createFunction({
14
+ id: deps.id ?? 'notify-approval-request',
15
+ retries: 3,
16
+ triggers: [{ event: 'notification/send-approval-request' }],
17
+ }, async ({ event, step, logger }) => {
18
+ const data = (event.data ?? {});
19
+ const approvalId = data.approvalId;
20
+ if (!approvalId) {
21
+ logger.warn('Skipping notify-approval-request without approvalId');
22
+ return { skipped: true };
23
+ }
24
+ const collectionSlug = deps.options.approvalsCollectionSlug ?? DEFAULT_APPROVALS_COLLECTION_SLUG;
25
+ const approval = await step.run('load-approval', async () => deps.payload.findByID({ collection: collectionSlug, id: approvalId, depth: 1 }));
26
+ if (!approval) {
27
+ logger.warn('Approval not found for notify-approval-request', { approvalId });
28
+ return { skipped: true, reason: 'approval-not-found' };
29
+ }
30
+ const approverIds = readApproverIds(approval['notifiedApprovers']);
31
+ if (approverIds.length === 0) {
32
+ logger.warn('No approvers to notify on approval', { approvalId });
33
+ return { sent: 0, reason: 'no-approvers' };
34
+ }
35
+ const requesterId = unwrapRelationshipId(approval['requestedBy']);
36
+ const requester = requesterId
37
+ ? await step.run('load-requester', () => deps.options.resolveRequester(requesterId))
38
+ : null;
39
+ const requesterName = requester?.name ?? requester?.email ?? 'A teammate';
40
+ const targetCollection = String(approval['targetCollection'] ?? '');
41
+ const targetId = String(approval['targetId'] ?? '');
42
+ const targetTitle = String(approval['targetTitle'] ?? '(untitled)');
43
+ const targetKind = targetKindFromCollection(targetCollection);
44
+ const changesSummary = String(approval['changesSummary'] ?? '');
45
+ const expiresAt = formatHumanDate(String(approval['expiresAt'] ?? ''));
46
+ const requestReason = typeof approval['requestReason'] === 'string' ? approval['requestReason'] : undefined;
47
+ let previewUrl = typeof approval['previewUrl'] === 'string' ? approval['previewUrl'] : '';
48
+ if (!previewUrl && deps.options.buildPreviewUrl) {
49
+ previewUrl = await step.run('build-preview-url', async () => deps.options.buildPreviewUrl({ targetCollection, targetId, approvalId }));
50
+ }
51
+ let sent = 0;
52
+ for (const approverId of approverIds) {
53
+ await step.run(`send-to-${approverId}`, async () => {
54
+ const approver = await deps.options.resolveApprover(approverId);
55
+ if (!approver) {
56
+ logger.warn('No email for approver; skipping', { approverId });
57
+ return;
58
+ }
59
+ const [approveUrl, changesUrl, discussUrl] = await Promise.all([
60
+ deps.options.buildActionUrl({ approvalId, action: 'approve', approverId }),
61
+ deps.options.buildActionUrl({ approvalId, action: 'changes', approverId }),
62
+ deps.options.buildActionUrl({ approvalId, action: 'discuss', approverId }),
63
+ ]);
64
+ await deps.client.send({
65
+ to: approver.email,
66
+ subject: `Approval needed: ${targetTitle}`,
67
+ template: ApprovalRequestEmail({
68
+ approverName: approver.name ?? approver.email,
69
+ requesterName,
70
+ targetTitle,
71
+ targetKind,
72
+ changesSummary,
73
+ ...(requestReason ? { requestReason } : {}),
74
+ previewUrl,
75
+ approveUrl,
76
+ changesUrl,
77
+ discussUrl,
78
+ expiresAt,
79
+ tokens: deps.tokens,
80
+ }),
81
+ tags: [
82
+ { name: 'type', value: 'approval-request' },
83
+ { name: 'approval-id', value: approvalId },
84
+ ],
85
+ });
86
+ sent += 1;
87
+ });
88
+ }
89
+ logger.info('Approval-request notifications sent', { approvalId, sent });
90
+ return { approvalId, sent };
91
+ });
92
+ }
93
+ //# sourceMappingURL=notify-approval-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-approval-request.js","sourceRoot":"","sources":["../../src/functions/notify-approval-request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export { emailPlugin, getEmailClient, getEmailFunctions } from './plugin.js';
2
+ export { createEmailClient } from './client.js';
3
+ export type { EmailClient, EmailClientOptions, SendEmailParams, SendEmailResult, TemplateRenderer, } from './client.js';
4
+ export { defaultTokens, mergeTokens } from './tokens.js';
5
+ export type { EmailBrandTokens } from './tokens.js';
6
+ export { DEFAULT_APPROVALS_COLLECTION_SLUG, validateOptions } from './options.js';
7
+ export type { ApprovalActionKind, BuildActionUrlArgs, EmailPluginOptions, ResolvedEmailEnv, ResolvedRecipient, } from './options.js';
8
+ export { createNotifyApprovalRequestFunction, createNotifyApprovalDecisionFunction, createNotifyApprovalExpiredFunction, } from './functions/index.js';
9
+ export { EmailLayout, ApprovalRequestEmail, ApprovalDecisionEmail, ApprovalExpiredEmail, } from './templates/index.js';
10
+ export type { ApprovalRequestEmailProps, ApprovalDecisionEmailProps, ApprovalDecisionKind, ApprovalExpiredEmailProps, ApprovalTargetKind, EmailLayoutProps, } from './templates/index.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,iCAAiC,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACjF,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,mCAAmC,EACnC,oCAAoC,EACpC,mCAAmC,GACpC,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { emailPlugin, getEmailClient, getEmailFunctions } from './plugin.js';
2
+ export { createEmailClient } from './client.js';
3
+ export { defaultTokens, mergeTokens } from './tokens.js';
4
+ export { DEFAULT_APPROVALS_COLLECTION_SLUG, validateOptions } from './options.js';
5
+ export { createNotifyApprovalRequestFunction, createNotifyApprovalDecisionFunction, createNotifyApprovalExpiredFunction, } from './functions/index.js';
6
+ export { EmailLayout, ApprovalRequestEmail, ApprovalDecisionEmail, ApprovalExpiredEmail, } from './templates/index.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAS/C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,iCAAiC,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AASjF,OAAO,EACL,mCAAmC,EACnC,oCAAoC,EACpC,mCAAmC,GACpC,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,79 @@
1
+ import type { Inngest } from 'inngest';
2
+ import type { BaseCorePluginOptions } from '@forumone/throughline-plugin-contract';
3
+ import type { EmailBrandTokens } from './tokens.js';
4
+ export type ApprovalActionKind = 'approve' | 'decline' | 'changes' | 'discuss';
5
+ export interface ResolvedRecipient {
6
+ email: string;
7
+ name?: string;
8
+ }
9
+ export interface BuildActionUrlArgs {
10
+ approvalId: string;
11
+ action: ApprovalActionKind;
12
+ approverId: string;
13
+ }
14
+ export interface EmailPluginOptions extends BaseCorePluginOptions {
15
+ /** Inngest client used to register the three notification functions. */
16
+ inngest: Inngest;
17
+ /** Resend API key. Falls back to `process.env.RESEND_API_KEY`. */
18
+ apiKey?: string;
19
+ /** From address (e.g. `notifications@example.com`). Falls back to `EMAIL_FROM_ADDRESS`. */
20
+ fromAddress?: string;
21
+ /**
22
+ * From display name. Falls back to `EMAIL_FROM_NAME` then `tokens.brandName`.
23
+ * Centralizing on `brandName` is intentional — keep "this came from <site>"
24
+ * consistent across header, From, and footer.
25
+ */
26
+ fromName?: string;
27
+ /** Reply-to. Falls back to `EMAIL_REPLY_TO`. */
28
+ replyTo?: string;
29
+ /**
30
+ * Brand-token overrides. Anything omitted falls back to the neutral defaults
31
+ * (black on white, system sans, "Your Site"). Pass an empty object to keep
32
+ * defaults entirely.
33
+ */
34
+ tokens?: Partial<EmailBrandTokens>;
35
+ /** Approvals collection slug. Default: 'approvals'. */
36
+ approvalsCollectionSlug?: string;
37
+ /**
38
+ * Resolves an approver's email + display name from a user ID. The plugin
39
+ * stays decoupled from Payload's user model — different deployments may
40
+ * have users in different collections with different field names.
41
+ */
42
+ resolveApprover: (userId: string) => Promise<ResolvedRecipient | null>;
43
+ /** Resolves a requester's email + display name from a user ID. */
44
+ resolveRequester: (userId: string) => Promise<ResolvedRecipient | null>;
45
+ /**
46
+ * Builds the URL for an approval action button (Approve, Request changes,
47
+ * Discuss, Decline). Default implementation lands in a follow-up; clients
48
+ * normally pass their own builder that wraps approvals' HMAC-signed token.
49
+ */
50
+ buildActionUrl: (args: BuildActionUrlArgs) => Promise<string>;
51
+ /**
52
+ * Builds a deployment-relative preview URL for the given target document.
53
+ * Default implementation builds `${NEXT_PUBLIC_SERVER_URL}/preview?...` if
54
+ * the env var is set; clients with bespoke previews override.
55
+ */
56
+ buildPreviewUrl?: (args: {
57
+ targetCollection: string;
58
+ targetId: string;
59
+ approvalId: string;
60
+ }) => Promise<string>;
61
+ }
62
+ export declare const DEFAULT_APPROVALS_COLLECTION_SLUG = "approvals";
63
+ export interface ResolvedEmailEnv {
64
+ apiKey: string;
65
+ fromAddress: string;
66
+ fromName: string;
67
+ replyTo?: string;
68
+ }
69
+ /**
70
+ * Validates options at plugin init. Resolves the Resend secrets via env
71
+ * fallback so missing configuration surfaces immediately rather than on the
72
+ * first Inngest invocation.
73
+ */
74
+ export declare function validateOptions(options: EmailPluginOptions): {
75
+ options: EmailPluginOptions;
76
+ env: ResolvedEmailEnv;
77
+ brandName: string;
78
+ };
79
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAE9E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAmB,SAAQ,qBAAqB;IAC/D,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAA;IAChB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAClC,uDAAuD;IACvD,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC;;;;OAIG;IACH,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACtE,kEAAkE;IAClE,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACvE;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7D;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACtB;AAED,eAAO,MAAM,iCAAiC,cAAc,CAAA;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG;IAC5D,OAAO,EAAE,kBAAkB,CAAA;IAC3B,GAAG,EAAE,gBAAgB,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB,CAwCA"}
@@ -0,0 +1,39 @@
1
+ export const DEFAULT_APPROVALS_COLLECTION_SLUG = 'approvals';
2
+ /**
3
+ * Validates options at plugin init. Resolves the Resend secrets via env
4
+ * fallback so missing configuration surfaces immediately rather than on the
5
+ * first Inngest invocation.
6
+ */
7
+ export function validateOptions(options) {
8
+ if (!options.inngest) {
9
+ throw new Error('emailPlugin requires an Inngest client (`options.inngest`).');
10
+ }
11
+ const apiKey = options.apiKey ?? process.env['RESEND_API_KEY'];
12
+ if (!apiKey) {
13
+ throw new Error('emailPlugin requires `options.apiKey` or the RESEND_API_KEY env var.');
14
+ }
15
+ const fromAddress = options.fromAddress ?? process.env['EMAIL_FROM_ADDRESS'];
16
+ if (!fromAddress) {
17
+ throw new Error('emailPlugin requires `options.fromAddress` or the EMAIL_FROM_ADDRESS env var.');
18
+ }
19
+ if (typeof options.resolveApprover !== 'function' || typeof options.resolveRequester !== 'function') {
20
+ throw new Error('emailPlugin requires `options.resolveApprover` and `options.resolveRequester` so the plugin can map user IDs to email addresses without coupling to a specific user-model schema.');
21
+ }
22
+ if (typeof options.buildActionUrl !== 'function') {
23
+ throw new Error('emailPlugin requires `options.buildActionUrl` so approval action buttons resolve to deployment-specific HMAC-signed URLs.');
24
+ }
25
+ const brandName = options.fromName ??
26
+ process.env['EMAIL_FROM_NAME'] ??
27
+ options.tokens?.brandName ??
28
+ 'Your Site';
29
+ const env = {
30
+ apiKey,
31
+ fromAddress,
32
+ fromName: brandName,
33
+ };
34
+ const replyTo = options.replyTo ?? process.env['EMAIL_REPLY_TO'];
35
+ if (replyTo)
36
+ env.replyTo = replyTo;
37
+ return { options, env, brandName };
38
+ }
39
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAkEA,MAAM,CAAC,MAAM,iCAAiC,GAAG,WAAW,CAAA;AAS5D;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAA2B;IAKzD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;IAChF,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAC9D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAA;IACzF,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IAC5E,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAA;IACH,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,eAAe,KAAK,UAAU,IAAI,OAAO,OAAO,CAAC,gBAAgB,KAAK,UAAU,EAAE,CAAC;QACpG,MAAM,IAAI,KAAK,CACb,mLAAmL,CACpL,CAAA;IACH,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,2HAA2H,CAC5H,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GACb,OAAO,CAAC,QAAQ;QAChB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAC9B,OAAO,CAAC,MAAM,EAAE,SAAS;QACzB,WAAW,CAAA;IAEb,MAAM,GAAG,GAAqB;QAC5B,MAAM;QACN,WAAW;QACX,QAAQ,EAAE,SAAS;KACpB,CAAA;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChE,IAAI,OAAO;QAAE,GAAG,CAAC,OAAO,GAAG,OAAO,CAAA;IAElC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAA;AACpC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { CorePlugin } from '@forumone/throughline-plugin-contract';
2
+ import type { InngestFunction } from 'inngest';
3
+ import { type EmailPluginOptions } from './options.js';
4
+ import { type EmailClient } from './client.js';
5
+ /**
6
+ * Email plugin. Validates configuration, instantiates the Resend-backed
7
+ * email client, and exposes the three notification Inngest functions
8
+ * (request / decision / expired) via Symbols so the client app's
9
+ * Inngest endpoint can register them alongside its other functions.
10
+ *
11
+ * Like the integrations plugin, this does not _serve_ Inngest — Payload
12
+ * plugins don't have a hook for the Next.js Inngest route. Use
13
+ * `getEmailFunctions(payload)` from your endpoint to compose them.
14
+ */
15
+ export declare const emailPlugin: CorePlugin<EmailPluginOptions>;
16
+ export declare function getEmailClient(payload: unknown): EmailClient | undefined;
17
+ export declare function getEmailFunctions(payload: unknown): InngestFunction.Any[];
18
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAA;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,KAAK,kBAAkB,EAAmB,MAAM,cAAc,CAAA;AAEvE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAajE;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,kBAAkB,CAwDpD,CAAA;AAEH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAExE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,CAMzE"}
package/dist/plugin.js ADDED
@@ -0,0 +1,76 @@
1
+ import { getPluginRegistry } from '@forumone/throughline-plugin-contract';
2
+ import { createNamedLogger, defaultLogger } from '@forumone/throughline-core';
3
+ import { validateOptions } from './options.js';
4
+ import { mergeTokens } from './tokens.js';
5
+ import { createEmailClient } from './client.js';
6
+ import { createNotifyApprovalDecisionFunction, createNotifyApprovalExpiredFunction, createNotifyApprovalRequestFunction, } from './functions/index.js';
7
+ const PLUGIN_ID = '@forumone/throughline-email';
8
+ const PLUGIN_VERSION = '0.1.0';
9
+ const EMAIL_CLIENT_SYMBOL = Symbol.for('@forumone/throughline/email-client');
10
+ const EMAIL_FUNCTIONS_SYMBOL = Symbol.for('@forumone/throughline/email-functions');
11
+ /**
12
+ * Email plugin. Validates configuration, instantiates the Resend-backed
13
+ * email client, and exposes the three notification Inngest functions
14
+ * (request / decision / expired) via Symbols so the client app's
15
+ * Inngest endpoint can register them alongside its other functions.
16
+ *
17
+ * Like the integrations plugin, this does not _serve_ Inngest — Payload
18
+ * plugins don't have a hook for the Next.js Inngest route. Use
19
+ * `getEmailFunctions(payload)` from your endpoint to compose them.
20
+ */
21
+ export const emailPlugin = (rawOptions) => (incomingConfig) => {
22
+ if (rawOptions.enabled === false)
23
+ return incomingConfig;
24
+ const { options, env, brandName } = validateOptions(rawOptions);
25
+ const logger = createNamedLogger('email', options.logger ?? defaultLogger);
26
+ const tokens = mergeTokens({ ...options.tokens, brandName });
27
+ return {
28
+ ...incomingConfig,
29
+ onInit: async (payload) => {
30
+ if (incomingConfig.onInit)
31
+ await incomingConfig.onInit(payload);
32
+ const registry = getPluginRegistry(payload);
33
+ const client = createEmailClient({
34
+ apiKey: env.apiKey,
35
+ fromAddress: env.fromAddress,
36
+ fromName: env.fromName,
37
+ ...(env.replyTo ? { defaultReplyTo: env.replyTo } : {}),
38
+ });
39
+ Object.defineProperty(payload, EMAIL_CLIENT_SYMBOL, {
40
+ value: client,
41
+ enumerable: false,
42
+ writable: false,
43
+ configurable: false,
44
+ });
45
+ const deps = { inngest: options.inngest, payload, client, tokens, options };
46
+ const functions = [
47
+ createNotifyApprovalRequestFunction(deps),
48
+ createNotifyApprovalDecisionFunction(deps),
49
+ createNotifyApprovalExpiredFunction(deps),
50
+ ];
51
+ Object.defineProperty(payload, EMAIL_FUNCTIONS_SYMBOL, {
52
+ value: functions,
53
+ enumerable: false,
54
+ writable: false,
55
+ configurable: false,
56
+ });
57
+ registry.register({
58
+ id: PLUGIN_ID,
59
+ version: PLUGIN_VERSION,
60
+ capabilities: ['email', 'notification-transport'],
61
+ });
62
+ logger.info('Email system ready', {
63
+ brandName: tokens.brandName,
64
+ from: env.fromAddress,
65
+ functions: functions.length,
66
+ });
67
+ },
68
+ };
69
+ };
70
+ export function getEmailClient(payload) {
71
+ return payload[EMAIL_CLIENT_SYMBOL];
72
+ }
73
+ export function getEmailFunctions(payload) {
74
+ return (payload[EMAIL_FUNCTIONS_SYMBOL] ?? []);
75
+ }
76
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { EmailBrandTokens } from '../tokens.js';
2
+ export type ApprovalDecisionKind = 'granted' | 'declined' | 'changes-requested';
3
+ export interface ApprovalDecisionEmailProps {
4
+ requesterName: string;
5
+ /** Display name of the person who decided. */
6
+ decidedBy: string;
7
+ targetTitle: string;
8
+ decision: ApprovalDecisionKind;
9
+ /** Free-form notes from the decider. Optional. */
10
+ decisionNotes?: string;
11
+ previewUrl: string;
12
+ tokens: EmailBrandTokens;
13
+ }
14
+ export declare function ApprovalDecisionEmail(props: ApprovalDecisionEmailProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=ApprovalDecision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalDecision.d.ts","sourceRoot":"","sources":["../../src/templates/ApprovalDecision.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,UAAU,GAAG,mBAAmB,CAAA;AAE/E,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAA;IACrB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,oBAAoB,CAAA;IAC9B,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAwBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,2CAwEtE"}
@@ -0,0 +1,56 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button, Heading, Section, Text } from '@react-email/components';
3
+ import { EmailLayout } from './_layout.js';
4
+ const HEADLINE = {
5
+ granted: 'Approved',
6
+ declined: 'Not approved',
7
+ 'changes-requested': 'Changes requested',
8
+ };
9
+ const HEADLINE_COLOR = {
10
+ granted: '#16A34A',
11
+ declined: '#DC2626',
12
+ 'changes-requested': '#CA8A04',
13
+ };
14
+ function nextStep(decision, target, decidedBy) {
15
+ if (decision === 'granted') {
16
+ return `You can publish ${target} when you're ready. Ask Claude to publish it.`;
17
+ }
18
+ if (decision === 'declined') {
19
+ return `${target} was not approved. Read the decision notes below and discuss with ${decidedBy} if you'd like context.`;
20
+ }
21
+ return `${decidedBy} requested changes to ${target}. Edit as requested and ask Claude to request approval again.`;
22
+ }
23
+ export function ApprovalDecisionEmail(props) {
24
+ const { tokens, decision } = props;
25
+ const headlineStyle = {
26
+ color: HEADLINE_COLOR[decision],
27
+ fontSize: 22,
28
+ margin: 0,
29
+ };
30
+ const labelStyle = {
31
+ color: tokens.textSecondary,
32
+ fontSize: 12,
33
+ fontWeight: 600,
34
+ margin: 0,
35
+ textTransform: 'uppercase',
36
+ letterSpacing: '0.05em',
37
+ };
38
+ const notesBoxStyle = {
39
+ backgroundColor: tokens.bgSecondary,
40
+ padding: 16,
41
+ borderRadius: 6,
42
+ marginTop: 16,
43
+ };
44
+ const previewButtonStyle = {
45
+ backgroundColor: tokens.textPrimary,
46
+ color: '#FFFFFF',
47
+ padding: '12px 20px',
48
+ borderRadius: 4,
49
+ textDecoration: 'none',
50
+ fontSize: 14,
51
+ fontWeight: 600,
52
+ display: 'inline-block',
53
+ };
54
+ return (_jsxs(EmailLayout, { preview: `${HEADLINE[decision]}: ${props.targetTitle}`, tokens: tokens, children: [_jsxs(Heading, { style: headlineStyle, children: [HEADLINE[decision], ": ", props.targetTitle] }), _jsxs(Text, { style: { color: tokens.textPrimary, fontSize: 16, marginTop: 16 }, children: ["Hi ", props.requesterName, ","] }), _jsxs(Text, { style: { color: tokens.textPrimary, fontSize: 16 }, children: [props.decidedBy, " reviewed your approval request."] }), props.decisionNotes ? (_jsxs(Section, { style: notesBoxStyle, children: [_jsxs(Text, { style: labelStyle, children: ["Notes from ", props.decidedBy] }), _jsx(Text, { style: { color: tokens.textPrimary, fontSize: 14, margin: '8px 0 0' }, children: props.decisionNotes })] })) : null, _jsxs(Text, { style: { color: tokens.textPrimary, fontSize: 14, marginTop: 24 }, children: [_jsx("strong", { children: "Next step:" }), " ", nextStep(decision, props.targetTitle, props.decidedBy)] }), decision !== 'declined' ? (_jsx(Section, { style: { marginTop: 16 }, children: _jsxs(Button, { href: props.previewUrl, style: previewButtonStyle, children: ["Preview ", props.targetTitle] }) })) : null] }));
55
+ }
56
+ //# sourceMappingURL=ApprovalDecision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalDecision.js","sourceRoot":"","sources":["../../src/templates/ApprovalDecision.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { EmailBrandTokens } from '../tokens.js';
2
+ export interface ApprovalExpiredEmailProps {
3
+ requesterName: string;
4
+ targetTitle: string;
5
+ /** Pre-formatted "requested on" date (e.g. "Apr 15, 2026"). */
6
+ requestedAt: string;
7
+ tokens: EmailBrandTokens;
8
+ }
9
+ export declare function ApprovalExpiredEmail(props: ApprovalExpiredEmailProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=ApprovalExpired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalExpired.d.ts","sourceRoot":"","sources":["../../src/templates/ApprovalExpired.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CAuBpE"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Heading, Text } from '@react-email/components';
3
+ import { EmailLayout } from './_layout.js';
4
+ export function ApprovalExpiredEmail(props) {
5
+ const { tokens } = props;
6
+ return (_jsxs(EmailLayout, { preview: `Approval expired: ${props.targetTitle}`, tokens: tokens, children: [_jsx(Heading, { style: { color: tokens.textPrimary, fontSize: 22, margin: 0 }, children: "Approval expired" }), _jsxs(Text, { style: { color: tokens.textPrimary, fontSize: 16, marginTop: 16 }, children: ["Hi ", props.requesterName, ","] }), _jsxs(Text, { style: { color: tokens.textPrimary, fontSize: 16 }, children: ["The approval request you submitted for ", _jsx("strong", { children: props.targetTitle }), " on", ' ', props.requestedAt, " has expired without a decision."] }), _jsx(Text, { style: { color: tokens.textPrimary, fontSize: 14, marginTop: 16 }, children: "If you still want to publish, ask Claude to request approval again \u2014 the framework will re-route to the same approver groups." })] }));
7
+ }
8
+ //# sourceMappingURL=ApprovalExpired.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalExpired.js","sourceRoot":"","sources":["../../src/templates/ApprovalExpired.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAW1C,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IACxB,OAAO,CACL,MAAC,WAAW,IAAC,OAAO,EAAE,qBAAqB,KAAK,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,aAC5E,KAAC,OAAO,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,iCAE5D,EAEV,MAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,oBACjE,KAAK,CAAC,aAAa,SAClB,EAEP,MAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,wDACf,2BAAS,KAAK,CAAC,WAAW,GAAU,SAAI,GAAG,EACjF,KAAK,CAAC,WAAW,wCACb,EAEP,KAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,mJAGhE,IACK,CACf,CAAA;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { EmailBrandTokens } from '../tokens.js';
2
+ export type ApprovalTargetKind = 'page' | 'post' | 'item';
3
+ export interface ApprovalRequestEmailProps {
4
+ approverName: string;
5
+ requesterName: string;
6
+ targetTitle: string;
7
+ /**
8
+ * Human-friendly noun for the target ("page", "post", "item"). Caller maps
9
+ * the collection slug; the template renders it verbatim.
10
+ */
11
+ targetKind: ApprovalTargetKind;
12
+ changesSummary: string;
13
+ /** Free-form reason from the requester. Optional. */
14
+ requestReason?: string;
15
+ previewUrl: string;
16
+ approveUrl: string;
17
+ changesUrl: string;
18
+ discussUrl: string;
19
+ /** Pre-formatted expiration string (e.g. `May 5, 2026`). */
20
+ expiresAt: string;
21
+ tokens: EmailBrandTokens;
22
+ }
23
+ export declare function ApprovalRequestEmail(props: ApprovalRequestEmailProps): import("react/jsx-runtime").JSX.Element;
24
+ //# sourceMappingURL=ApprovalRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalRequest.d.ts","sourceRoot":"","sources":["../../src/templates/ApprovalRequest.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAEzD,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,UAAU,EAAE,kBAAkB,CAAA;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CA6HpE"}