@openlup/platform 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +285 -5
- package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
- package/dist/communications/accountingDocumentDelivery.js +33 -0
- package/dist/communications/authEmailError.d.ts +5 -0
- package/dist/communications/authEmailError.js +8 -0
- package/dist/communications/authEmailPorts.d.ts +2 -0
- package/dist/communications/authEmailPorts.js +1 -0
- package/dist/communications/authSendEmailHook.d.ts +35 -0
- package/dist/communications/authSendEmailHook.js +250 -0
- package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
- package/dist/communications/capturedTransactionalDelivery.js +14 -0
- package/dist/communications/communicationSync.d.ts +11 -0
- package/dist/communications/communicationSync.js +79 -0
- package/dist/communications/communicationSyncWorker.d.ts +68 -0
- package/dist/communications/communicationSyncWorker.js +218 -0
- package/dist/communications/consentPolicy.d.ts +19 -0
- package/dist/communications/consentPolicy.js +43 -0
- package/dist/communications/consentPurposeRegistry.d.ts +4 -0
- package/dist/communications/consentPurposeRegistry.js +24 -0
- package/dist/communications/contracts/abandonedCart.d.ts +27 -0
- package/dist/communications/contracts/abandonedCart.js +25 -0
- package/dist/communications/contracts/authEmail.d.ts +62 -0
- package/dist/communications/contracts/authEmail.js +1 -0
- package/dist/communications/contracts/backInStock.d.ts +28 -0
- package/dist/communications/contracts/backInStock.js +28 -0
- package/dist/communications/contracts/blocks.d.ts +24 -0
- package/dist/communications/contracts/blocks.js +23 -0
- package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
- package/dist/communications/contracts/checkoutExpired.js +25 -0
- package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
- package/dist/communications/contracts/checkoutRecovery.js +37 -0
- package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
- package/dist/communications/contracts/commerceEmailContent.js +1 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
- package/dist/communications/contracts/consent.d.ts +12 -0
- package/dist/communications/contracts/consent.js +1 -0
- package/dist/communications/contracts/email.d.ts +182 -0
- package/dist/communications/contracts/email.js +1 -0
- package/dist/communications/contracts/guideCard.d.ts +3 -0
- package/dist/communications/contracts/guideCard.js +26 -0
- package/dist/communications/contracts/index.d.ts +51 -0
- package/dist/communications/contracts/index.js +42 -0
- package/dist/communications/contracts/inquiryContent.d.ts +32 -0
- package/dist/communications/contracts/inquiryContent.js +33 -0
- package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
- package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
- package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
- package/dist/communications/contracts/marketingAutomation.js +1 -0
- package/dist/communications/contracts/newsletter.d.ts +63 -0
- package/dist/communications/contracts/newsletter.js +1 -0
- package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
- package/dist/communications/contracts/newsletterWebhook.js +1 -0
- package/dist/communications/contracts/optOutContent.d.ts +11 -0
- package/dist/communications/contracts/optOutContent.js +17 -0
- package/dist/communications/contracts/orderCanceled.d.ts +25 -0
- package/dist/communications/contracts/orderCanceled.js +27 -0
- package/dist/communications/contracts/orderDraft.d.ts +46 -0
- package/dist/communications/contracts/orderDraft.js +46 -0
- package/dist/communications/contracts/orderPaid.d.ts +53 -0
- package/dist/communications/contracts/orderPaid.js +71 -0
- package/dist/communications/contracts/orderRefunded.d.ts +24 -0
- package/dist/communications/contracts/orderRefunded.js +27 -0
- package/dist/communications/contracts/paymentFailed.d.ts +24 -0
- package/dist/communications/contracts/paymentFailed.js +24 -0
- package/dist/communications/contracts/publicIntake.d.ts +49 -0
- package/dist/communications/contracts/publicIntake.js +1 -0
- package/dist/communications/contracts/render.d.ts +2 -0
- package/dist/communications/contracts/render.js +229 -0
- package/dist/communications/contracts/reorderReminder.d.ts +24 -0
- package/dist/communications/contracts/reorderReminder.js +24 -0
- package/dist/communications/contracts/returnEmails.d.ts +25 -0
- package/dist/communications/contracts/returnEmails.js +62 -0
- package/dist/communications/contracts/reviewEffects.d.ts +26 -0
- package/dist/communications/contracts/reviewEffects.js +25 -0
- package/dist/communications/contracts/reviewRequest.d.ts +26 -0
- package/dist/communications/contracts/reviewRequest.js +24 -0
- package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
- package/dist/communications/contracts/roleNotificationContent.js +46 -0
- package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
- package/dist/communications/contracts/shipmentDelivered.js +24 -0
- package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
- package/dist/communications/contracts/shipmentDispatched.js +35 -0
- package/dist/communications/contracts/shipmentException.d.ts +21 -0
- package/dist/communications/contracts/shipmentException.js +26 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
- package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
- package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
- package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
- package/dist/communications/contracts/subscriptionCancelled.js +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
- package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
- package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
- package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
- package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
- package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
- package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
- package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
- package/dist/communications/contracts/subscriptionPaused.js +25 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
- package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
- package/dist/communications/contracts/subscriptionResumed.js +25 -0
- package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
- package/dist/communications/contracts/subscriptionWelcome.js +45 -0
- package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
- package/dist/communications/contracts/subscriptionWinback.js +28 -0
- package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
- package/dist/communications/contracts/transactionalDelivery.js +1 -0
- package/dist/communications/contracts/types.d.ts +46 -0
- package/dist/communications/contracts/types.js +1 -0
- package/dist/communications/contribution.d.ts +10 -0
- package/dist/communications/contribution.js +24 -0
- package/dist/communications/deliveryReconciliation.d.ts +64 -0
- package/dist/communications/deliveryReconciliation.js +60 -0
- package/dist/communications/emailEgress.d.ts +38 -0
- package/dist/communications/emailEgress.js +100 -0
- package/dist/communications/emailHealthAssessment.d.ts +39 -0
- package/dist/communications/emailHealthAssessment.js +59 -0
- package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
- package/dist/communications/emailHealthMetricsPort.js +1 -0
- package/dist/communications/emailNotificationControl.d.ts +27 -0
- package/dist/communications/emailNotificationControl.js +10 -0
- package/dist/communications/emailObservation.d.ts +28 -0
- package/dist/communications/emailObservation.js +23 -0
- package/dist/communications/emailTransport.d.ts +82 -0
- package/dist/communications/emailTransport.js +95 -0
- package/dist/communications/errors.d.ts +15 -0
- package/dist/communications/errors.js +30 -0
- package/dist/communications/index.d.ts +46 -0
- package/dist/communications/index.js +38 -0
- package/dist/communications/integrationEventSignature.d.ts +15 -0
- package/dist/communications/integrationEventSignature.js +37 -0
- package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
- package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
- package/dist/communications/nativeOperations.d.ts +79 -0
- package/dist/communications/nativeOperations.js +3 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
- package/dist/communications/newsletterIntegrationContracts.js +128 -0
- package/dist/communications/newsletterWebhookPort.d.ts +11 -0
- package/dist/communications/newsletterWebhookPort.js +84 -0
- package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
- package/dist/communications/newsletterWebhookProcessing.js +71 -0
- package/dist/communications/originPolicy.d.ts +40 -0
- package/dist/communications/originPolicy.js +100 -0
- package/dist/communications/postgresCommunicationSync.d.ts +79 -0
- package/dist/communications/postgresCommunicationSync.js +111 -0
- package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
- package/dist/communications/postgresConsentEvaluator.js +43 -0
- package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
- package/dist/communications/postgresDeliveryReconciliation.js +113 -0
- package/dist/communications/postgresNativeOperations.d.ts +13 -0
- package/dist/communications/postgresNativeOperations.js +63 -0
- package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
- package/dist/communications/postgresNotificationRecipients.js +60 -0
- package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
- package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
- package/dist/communications/preferencesToken.d.ts +10 -0
- package/dist/communications/preferencesToken.js +40 -0
- package/dist/communications/publicPreferenceToken.d.ts +24 -0
- package/dist/communications/publicPreferenceToken.js +53 -0
- package/dist/communications/shipmentNotifications.d.ts +34 -0
- package/dist/communications/shipmentNotifications.js +61 -0
- package/dist/communications/sql.d.ts +26 -0
- package/dist/communications/sql.js +7 -0
- package/dist/communications/standardWebhooks.d.ts +14 -0
- package/dist/communications/standardWebhooks.js +73 -0
- package/dist/communications/status.d.ts +11 -0
- package/dist/communications/status.js +36 -0
- package/dist/communications/transactionalDelivery.d.ts +19 -0
- package/dist/communications/transactionalDelivery.js +96 -0
- package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
- package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
- package/dist/communications/unsubscribeToken.d.ts +77 -0
- package/dist/communications/unsubscribeToken.js +150 -0
- package/dist/communications/validation.d.ts +2 -0
- package/dist/communications/validation.js +3 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/webhookSignature.d.ts +42 -0
- package/dist/http/webhookSignature.js +86 -0
- package/package.json +12 -2
- package/release-gates.json +41 -8
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20261019200000_communications_native_operations.sql +68 -0
- package/src/communications/accountingDocumentDelivery.ts +63 -0
- package/src/communications/authEmailError.ts +10 -0
- package/src/communications/authEmailPorts.ts +2 -0
- package/src/communications/authSendEmailHook.ts +310 -0
- package/src/communications/capturedTransactionalDelivery.ts +29 -0
- package/src/communications/communicationSync.ts +96 -0
- package/src/communications/communicationSyncWorker.ts +302 -0
- package/src/communications/consentPolicy.ts +82 -0
- package/src/communications/consentPurposeRegistry.ts +31 -0
- package/src/communications/contracts/abandonedCart.ts +64 -0
- package/src/communications/contracts/authEmail.ts +73 -0
- package/src/communications/contracts/backInStock.ts +69 -0
- package/src/communications/contracts/blocks.ts +47 -0
- package/src/communications/contracts/checkoutExpired.ts +62 -0
- package/src/communications/contracts/checkoutRecovery.ts +68 -0
- package/src/communications/contracts/commerceEmailContent.ts +189 -0
- package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
- package/src/communications/contracts/consent.ts +13 -0
- package/src/communications/contracts/email.ts +210 -0
- package/src/communications/contracts/guideCard.ts +34 -0
- package/src/communications/contracts/index.ts +51 -0
- package/src/communications/contracts/inquiryContent.ts +53 -0
- package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
- package/src/communications/contracts/marketingAutomation.ts +45 -0
- package/src/communications/contracts/newsletter.ts +78 -0
- package/src/communications/contracts/newsletterWebhook.ts +23 -0
- package/src/communications/contracts/optOutContent.ts +23 -0
- package/src/communications/contracts/orderCanceled.ts +64 -0
- package/src/communications/contracts/orderDraft.ts +112 -0
- package/src/communications/contracts/orderPaid.ts +147 -0
- package/src/communications/contracts/orderRefunded.ts +63 -0
- package/src/communications/contracts/paymentFailed.ts +59 -0
- package/src/communications/contracts/publicIntake.ts +53 -0
- package/src/communications/contracts/render.ts +271 -0
- package/src/communications/contracts/reorderReminder.ts +59 -0
- package/src/communications/contracts/returnEmails.ts +113 -0
- package/src/communications/contracts/reviewEffects.ts +60 -0
- package/src/communications/contracts/reviewRequest.ts +59 -0
- package/src/communications/contracts/roleNotificationContent.ts +65 -0
- package/src/communications/contracts/shipmentDelivered.ts +59 -0
- package/src/communications/contracts/shipmentDispatched.ts +77 -0
- package/src/communications/contracts/shipmentException.ts +62 -0
- package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
- package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
- package/src/communications/contracts/subscriptionCancelled.ts +57 -0
- package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
- package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
- package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
- package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
- package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
- package/src/communications/contracts/subscriptionPaused.ts +58 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
- package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
- package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
- package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
- package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
- package/src/communications/contracts/subscriptionResumed.ts +60 -0
- package/src/communications/contracts/subscriptionWelcome.ts +102 -0
- package/src/communications/contracts/subscriptionWinback.ts +62 -0
- package/src/communications/contracts/transactionalDelivery.ts +43 -0
- package/src/communications/contracts/types.ts +58 -0
- package/src/communications/contribution.ts +33 -0
- package/src/communications/deliveryReconciliation.ts +108 -0
- package/src/communications/emailEgress.ts +111 -0
- package/src/communications/emailHealthAssessment.ts +106 -0
- package/src/communications/emailHealthMetricsPort.ts +17 -0
- package/src/communications/emailNotificationControl.ts +28 -0
- package/src/communications/emailObservation.ts +43 -0
- package/src/communications/emailTransport.ts +167 -0
- package/src/communications/errors.ts +34 -0
- package/src/communications/index.ts +46 -0
- package/src/communications/integrationEventSignature.ts +49 -0
- package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
- package/src/communications/nativeOperations.ts +40 -0
- package/src/communications/newsletterIntegrationContracts.ts +154 -0
- package/src/communications/newsletterWebhookPort.ts +104 -0
- package/src/communications/newsletterWebhookProcessing.ts +102 -0
- package/src/communications/originPolicy.ts +151 -0
- package/src/communications/postgresCommunicationSync.ts +127 -0
- package/src/communications/postgresConsentEvaluator.ts +49 -0
- package/src/communications/postgresDeliveryReconciliation.ts +101 -0
- package/src/communications/postgresNativeOperations.ts +55 -0
- package/src/communications/postgresNotificationRecipients.ts +57 -0
- package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
- package/src/communications/preferencesToken.ts +62 -0
- package/src/communications/publicPreferenceToken.ts +68 -0
- package/src/communications/shipmentNotifications.ts +78 -0
- package/src/communications/sql.ts +7 -0
- package/src/communications/standardWebhooks.ts +84 -0
- package/src/communications/status.ts +51 -0
- package/src/communications/transactionalDelivery.ts +114 -0
- package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
- package/src/communications/unsubscribeToken.ts +186 -0
- package/src/communications/validation.ts +3 -0
- package/src/http/index.ts +1 -0
- package/src/http/webhookSignature.ts +96 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CommerceEmailContent } from "./commerceEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import {
|
|
4
|
+
accentBox,
|
|
5
|
+
button,
|
|
6
|
+
heading,
|
|
7
|
+
paragraph,
|
|
8
|
+
type EmailBlock,
|
|
9
|
+
} from "./blocks.js";
|
|
10
|
+
|
|
11
|
+
export interface CheckoutExpiredEmailVars {
|
|
12
|
+
firstName: string | null;
|
|
13
|
+
orderId: string;
|
|
14
|
+
amountLabel: string | null;
|
|
15
|
+
ctaUrl?: string | null;
|
|
16
|
+
ctaKind?: "recovery" | "compose";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CheckoutExpiredEmailContent {
|
|
20
|
+
subject: string;
|
|
21
|
+
preheader: string;
|
|
22
|
+
blocks: EmailBlock[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
26
|
+
export function createCheckoutExpiredContentFactory(input: {
|
|
27
|
+
commerceEmailContent: Pick<CommerceEmailContent, "checkoutExpired" | "orderRefPrefix">;
|
|
28
|
+
formatCustomerOrderReference: (orderId: string, prefix: string) => string;
|
|
29
|
+
}) {
|
|
30
|
+
const { commerceEmailContent, formatCustomerOrderReference } = input;
|
|
31
|
+
|
|
32
|
+
function checkoutExpiredEmailContent(
|
|
33
|
+
locale: Locale,
|
|
34
|
+
vars: CheckoutExpiredEmailVars,
|
|
35
|
+
signoff: string,
|
|
36
|
+
): CheckoutExpiredEmailContent {
|
|
37
|
+
const copy = commerceEmailContent.checkoutExpired[
|
|
38
|
+
vars.ctaKind === "recovery" ? "recovery" : "expired"
|
|
39
|
+
][locale];
|
|
40
|
+
const orderRef = formatCustomerOrderReference(
|
|
41
|
+
vars.orderId,
|
|
42
|
+
commerceEmailContent.orderRefPrefix,
|
|
43
|
+
);
|
|
44
|
+
const blocks: EmailBlock[] = [
|
|
45
|
+
heading(copy.heading),
|
|
46
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
47
|
+
paragraph(copy.intro(orderRef)),
|
|
48
|
+
accentBox(copy.details(vars.amountLabel), { label: copy.detailsLabel }),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
if (vars.ctaUrl) blocks.push(button(copy.cta, vars.ctaUrl));
|
|
52
|
+
blocks.push(paragraph(copy.outro));
|
|
53
|
+
blocks.push(paragraph(signoff));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
subject: copy.subject(orderRef),
|
|
57
|
+
preheader: copy.preheader,
|
|
58
|
+
blocks,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { checkoutExpiredEmailContent };
|
|
62
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CommerceEmailContent } from "./commerceEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { button, heading, paragraph, type EmailBlock } from "./blocks.js";
|
|
4
|
+
|
|
5
|
+
export type CheckoutRecoveryReminderHours = 1 | 20;
|
|
6
|
+
export type CheckoutRecoveryMode = "subscription_cycle" | "one_time";
|
|
7
|
+
|
|
8
|
+
export interface CheckoutRecoveryEmailVars {
|
|
9
|
+
firstName: string | null;
|
|
10
|
+
brandName: string;
|
|
11
|
+
reminderHours: CheckoutRecoveryReminderHours;
|
|
12
|
+
mode: CheckoutRecoveryMode;
|
|
13
|
+
/** Absolute signed deep-link to complete payment for this order. */
|
|
14
|
+
recoveryUrl: string;
|
|
15
|
+
/** Optional deployment-specific context consumed by the selected copy pack. */
|
|
16
|
+
contextName?: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CheckoutRecoveryEmailContent {
|
|
20
|
+
subject: string;
|
|
21
|
+
preheader: string;
|
|
22
|
+
blocks: EmailBlock[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
26
|
+
export function createCheckoutRecoveryContentFactory(input: {
|
|
27
|
+
commerceEmailContent: Pick<CommerceEmailContent, "checkoutRecovery">;
|
|
28
|
+
}) {
|
|
29
|
+
const { commerceEmailContent } = input;
|
|
30
|
+
|
|
31
|
+
function checkoutRecoveryEmailContent(
|
|
32
|
+
locale: Locale,
|
|
33
|
+
vars: CheckoutRecoveryEmailVars,
|
|
34
|
+
signoff: string,
|
|
35
|
+
): CheckoutRecoveryEmailContent {
|
|
36
|
+
const copy = commerceEmailContent.checkoutRecovery[locale];
|
|
37
|
+
const isFinal = vars.reminderHours === 20;
|
|
38
|
+
// The optional selected-pack context note runs only on the first nudge (1h).
|
|
39
|
+
// The final reminder (20h) stays businesslike, so it never sits next to the
|
|
40
|
+
// "order will be canceled" note.
|
|
41
|
+
const showCheer = !isFinal;
|
|
42
|
+
const isSubscription = vars.mode === "subscription_cycle";
|
|
43
|
+
const intro = isSubscription
|
|
44
|
+
? copy.introSubscription
|
|
45
|
+
: showCheer
|
|
46
|
+
? `${copy.introOneTime} ${copy.contextCheer(vars.contextName ?? null)}`
|
|
47
|
+
: copy.introOneTime;
|
|
48
|
+
|
|
49
|
+
const blocks: EmailBlock[] = [
|
|
50
|
+
heading(isFinal ? copy.headingFinal : copy.headingFirst),
|
|
51
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
52
|
+
paragraph(intro),
|
|
53
|
+
button(copy.cta, vars.recoveryUrl),
|
|
54
|
+
];
|
|
55
|
+
if (isSubscription && showCheer) {
|
|
56
|
+
blocks.push(paragraph(copy.subscriptionCheer(vars.contextName ?? null, vars.brandName)));
|
|
57
|
+
}
|
|
58
|
+
if (isFinal) blocks.push(paragraph(copy.finalNote));
|
|
59
|
+
blocks.push(paragraph(signoff));
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
subject: isFinal ? copy.subjectFinal : copy.subjectFirst,
|
|
63
|
+
preheader: copy.preheader,
|
|
64
|
+
blocks,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { checkoutRecoveryEmailContent };
|
|
68
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Locale } from "./email.js";
|
|
2
|
+
import type { CommerceEngagementEmailContent, CommerceRecoveryEmailContent } from "./commerceEngagementEmailContent.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export interface OrderDraftEmailCopy {
|
|
6
|
+
subject: (orderId: string) => string;
|
|
7
|
+
preheader: string;
|
|
8
|
+
heading: string;
|
|
9
|
+
greeting: (firstName: string | null) => string;
|
|
10
|
+
intro: (orderId: string) => string;
|
|
11
|
+
itemsLabel: string;
|
|
12
|
+
noItems: string;
|
|
13
|
+
subtotalLabel: string;
|
|
14
|
+
discountRowLabel: string;
|
|
15
|
+
totalLabel: string;
|
|
16
|
+
cta: string;
|
|
17
|
+
outro: string;
|
|
18
|
+
personalizedClosing: (name: string) => string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OrderPaidEmailCopy {
|
|
22
|
+
subject: (orderId: string) => string;
|
|
23
|
+
preheader: string;
|
|
24
|
+
heading: string;
|
|
25
|
+
greeting: (firstName: string | null) => string;
|
|
26
|
+
introOneTime: (orderId: string) => string;
|
|
27
|
+
introSubscription: (orderId: string) => string;
|
|
28
|
+
noItems: string;
|
|
29
|
+
subtotalLabel: string;
|
|
30
|
+
catalogProductsLabel: string;
|
|
31
|
+
discountRowLabel: string;
|
|
32
|
+
firstSubscriptionDiscountRowLabel: string;
|
|
33
|
+
productPayableLabel: string;
|
|
34
|
+
shippingLabel: string;
|
|
35
|
+
shippingFree: string;
|
|
36
|
+
totalLabel: string;
|
|
37
|
+
finalPaidLabel: string;
|
|
38
|
+
cta: string;
|
|
39
|
+
receiptNote: (name: string | null) => string;
|
|
40
|
+
outro: string;
|
|
41
|
+
withdrawalNotice: string;
|
|
42
|
+
withdrawalLinkLabel: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OrderCanceledEmailCopy {
|
|
46
|
+
subject: (orderId: string) => string;
|
|
47
|
+
preheader: string;
|
|
48
|
+
heading: string;
|
|
49
|
+
greeting: (firstName: string | null) => string;
|
|
50
|
+
intro: (orderId: string) => string;
|
|
51
|
+
detailsLabel: string;
|
|
52
|
+
details: (amountLabel: string | null) => string[];
|
|
53
|
+
returnNote: (name: string | null) => string;
|
|
54
|
+
cta: string;
|
|
55
|
+
outro: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface OrderRefundedEmailCopy {
|
|
59
|
+
subject: (orderId: string) => string;
|
|
60
|
+
preheader: string;
|
|
61
|
+
heading: string;
|
|
62
|
+
greeting: (firstName: string | null) => string;
|
|
63
|
+
intro: (orderId: string) => string;
|
|
64
|
+
detailsLabel: string;
|
|
65
|
+
details: (amountLabel: string | null) => string[];
|
|
66
|
+
returnNote: (brandName: string) => string;
|
|
67
|
+
cta: string;
|
|
68
|
+
outro: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PaymentFailedEmailCopy {
|
|
72
|
+
subject: (orderId: string) => string;
|
|
73
|
+
preheader: string;
|
|
74
|
+
heading: string;
|
|
75
|
+
greeting: (firstName: string | null) => string;
|
|
76
|
+
intro: (orderId: string) => string;
|
|
77
|
+
detailsLabel: string;
|
|
78
|
+
details: (amountLabel: string | null, mode: "subscription_cycle" | "one_time") => string[];
|
|
79
|
+
cta: string;
|
|
80
|
+
outro: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A guide the mail points the customer at: a document the deployment hosts
|
|
85
|
+
* under its own site origin, so `path` is site-relative and the content
|
|
86
|
+
* module joins it with the origin the sender passes in.
|
|
87
|
+
*/
|
|
88
|
+
export interface EmailGuideCardCopy {
|
|
89
|
+
eyebrow: string;
|
|
90
|
+
title: string;
|
|
91
|
+
text: (contextName: string | null) => string;
|
|
92
|
+
cta: string;
|
|
93
|
+
/** Site-relative path of the document (leading slash). */
|
|
94
|
+
path: string;
|
|
95
|
+
/** Optional illustration, also site-relative. */
|
|
96
|
+
image?: { path: string; alt: string; widthPx: number; heightPx: number };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ShipmentDispatchedEmailCopy {
|
|
100
|
+
subject: (orderRef: string) => string;
|
|
101
|
+
preheader: string;
|
|
102
|
+
heading: string;
|
|
103
|
+
greeting: (firstName: string | null) => string;
|
|
104
|
+
intro: (orderRef: string) => string;
|
|
105
|
+
trackingLabel: string;
|
|
106
|
+
trackParcel: string;
|
|
107
|
+
noTracking: string[];
|
|
108
|
+
noTrackingLabel: string;
|
|
109
|
+
/** The account guide the customer can read while the parcel travels. */
|
|
110
|
+
accountGuide: EmailGuideCardCopy;
|
|
111
|
+
outro: (contextName: string | null) => string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ShipmentDeliveredEmailCopy {
|
|
115
|
+
subject: (orderRef: string) => string;
|
|
116
|
+
preheader: string;
|
|
117
|
+
heading: string;
|
|
118
|
+
greeting: (firstName: string | null) => string;
|
|
119
|
+
intro: (orderRef: string, contextName: string | null) => string;
|
|
120
|
+
/** The getting-started guide to read before the first serving. */
|
|
121
|
+
startGuide: EmailGuideCardCopy;
|
|
122
|
+
outro: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ShipmentExceptionEmailCopy {
|
|
126
|
+
subject: (orderRef: string) => string;
|
|
127
|
+
preheader: string;
|
|
128
|
+
heading: string;
|
|
129
|
+
greeting: (firstName: string | null) => string;
|
|
130
|
+
intro: (orderRef: string) => string;
|
|
131
|
+
reassuranceLabel: string;
|
|
132
|
+
reassurance: string[];
|
|
133
|
+
cta: string;
|
|
134
|
+
outro: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ReturnApprovedEmailCopy {
|
|
138
|
+
subject: (orderRef: string) => string;
|
|
139
|
+
preheader: string;
|
|
140
|
+
heading: string;
|
|
141
|
+
greeting: (firstName: string | null) => string;
|
|
142
|
+
intro: (orderRef: string) => string;
|
|
143
|
+
returnAddressNotice: string;
|
|
144
|
+
cta: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ReturnRejectedEmailCopy {
|
|
148
|
+
subject: (orderRef: string) => string;
|
|
149
|
+
preheader: string;
|
|
150
|
+
heading: string;
|
|
151
|
+
greeting: (firstName: string | null) => string;
|
|
152
|
+
intro: (orderRef: string) => string;
|
|
153
|
+
cta: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ReturnRefundedEmailCopy {
|
|
157
|
+
subject: (orderRef: string) => string;
|
|
158
|
+
preheader: string;
|
|
159
|
+
heading: string;
|
|
160
|
+
greeting: (firstName: string | null) => string;
|
|
161
|
+
amountKnown: (amountLabel: string, orderRef: string) => string;
|
|
162
|
+
amountUnknown: (orderRef: string) => string;
|
|
163
|
+
returnNote: string;
|
|
164
|
+
cta: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Build-time selected transactional-copy owner for commerce mail. */
|
|
168
|
+
export interface CommerceEmailContent extends CommerceRecoveryEmailContent, CommerceEngagementEmailContent {
|
|
169
|
+
readonly id: string;
|
|
170
|
+
readonly orderRefPrefix: string;
|
|
171
|
+
readonly orderDraft: Record<Locale, OrderDraftEmailCopy>;
|
|
172
|
+
readonly orderPaid: Record<Locale, OrderPaidEmailCopy>;
|
|
173
|
+
readonly orderCanceled: Record<Locale, OrderCanceledEmailCopy>;
|
|
174
|
+
readonly orderRefunded: Record<Locale, OrderRefundedEmailCopy>;
|
|
175
|
+
readonly paymentFailed: Record<Locale, PaymentFailedEmailCopy>;
|
|
176
|
+
readonly shipmentDispatched: Record<Locale, ShipmentDispatchedEmailCopy>;
|
|
177
|
+
readonly shipmentDelivered: Record<Locale, ShipmentDeliveredEmailCopy>;
|
|
178
|
+
readonly shipmentException: Record<Locale, ShipmentExceptionEmailCopy>;
|
|
179
|
+
readonly returns: {
|
|
180
|
+
readonly approved: Record<Locale, ReturnApprovedEmailCopy>;
|
|
181
|
+
readonly rejected: Record<Locale, ReturnRejectedEmailCopy>;
|
|
182
|
+
readonly refunded: Record<Locale, ReturnRefundedEmailCopy>;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type CommerceFulfillmentEmailContent = Pick<
|
|
187
|
+
CommerceEmailContent,
|
|
188
|
+
"shipmentDispatched" | "shipmentDelivered" | "shipmentException" | "returns"
|
|
189
|
+
>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Locale } from "./email.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export interface AbandonedCartToneEmailCopy {
|
|
5
|
+
subject: string;
|
|
6
|
+
preheader: string;
|
|
7
|
+
heading: string;
|
|
8
|
+
intro: string;
|
|
9
|
+
body: string;
|
|
10
|
+
cta: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AbandonedCartEmailCopy {
|
|
14
|
+
greeting: (firstName: string | null) => string;
|
|
15
|
+
first: AbandonedCartToneEmailCopy;
|
|
16
|
+
gentle: AbandonedCartToneEmailCopy;
|
|
17
|
+
last: AbandonedCartToneEmailCopy;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BackInStockEmailCopy {
|
|
21
|
+
genericLabel: string;
|
|
22
|
+
subject: (productLabel: string) => string;
|
|
23
|
+
preheader: string;
|
|
24
|
+
heading: (productLabel: string) => string;
|
|
25
|
+
greeting: string;
|
|
26
|
+
intro: (productLabel: string) => string;
|
|
27
|
+
hook: string;
|
|
28
|
+
cta: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CheckoutExpiredEmailCopy {
|
|
32
|
+
subject: (orderRef: string) => string;
|
|
33
|
+
preheader: string;
|
|
34
|
+
heading: string;
|
|
35
|
+
greeting: (firstName: string | null) => string;
|
|
36
|
+
intro: (orderRef: string) => string;
|
|
37
|
+
detailsLabel: string;
|
|
38
|
+
details: (amountLabel: string | null) => string[];
|
|
39
|
+
cta: string;
|
|
40
|
+
outro: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CheckoutRecoveryEmailCopy {
|
|
44
|
+
greeting: (firstName: string | null) => string;
|
|
45
|
+
cta: string;
|
|
46
|
+
subjectFirst: string;
|
|
47
|
+
subjectFinal: string;
|
|
48
|
+
preheader: string;
|
|
49
|
+
headingFirst: string;
|
|
50
|
+
headingFinal: string;
|
|
51
|
+
introSubscription: string;
|
|
52
|
+
introOneTime: string;
|
|
53
|
+
contextCheer: (contextName: string | null) => string;
|
|
54
|
+
subscriptionCheer: (contextName: string | null, appName: string) => string;
|
|
55
|
+
finalNote: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ReorderReminderEmailCopy {
|
|
59
|
+
subject: string;
|
|
60
|
+
preheader: string;
|
|
61
|
+
greeting: (firstName: string | null) => string;
|
|
62
|
+
heading: string;
|
|
63
|
+
intro: (appName: string) => string;
|
|
64
|
+
consistencyNote: string;
|
|
65
|
+
upsellLabel: string;
|
|
66
|
+
upsellLines: string[];
|
|
67
|
+
cta: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ReviewRequestEmailCopy {
|
|
71
|
+
subject: string;
|
|
72
|
+
preheader: string;
|
|
73
|
+
heading: string;
|
|
74
|
+
greeting: (firstName: string | null) => string;
|
|
75
|
+
intro: (contextName: string | null) => string;
|
|
76
|
+
body: string;
|
|
77
|
+
cta: string;
|
|
78
|
+
outro: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ReviewEffectsEmailCopy {
|
|
82
|
+
subject: string;
|
|
83
|
+
preheader: string;
|
|
84
|
+
heading: string;
|
|
85
|
+
greeting: (firstName: string | null) => string;
|
|
86
|
+
intro: (contextName: string | null, appName: string) => string;
|
|
87
|
+
body: string;
|
|
88
|
+
cta: string;
|
|
89
|
+
outro: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CommerceRecoveryEmailContent {
|
|
93
|
+
readonly abandonedCart: Record<Locale, AbandonedCartEmailCopy>;
|
|
94
|
+
readonly checkoutExpired: {
|
|
95
|
+
readonly expired: Record<Locale, CheckoutExpiredEmailCopy>;
|
|
96
|
+
readonly recovery: Record<Locale, CheckoutExpiredEmailCopy>;
|
|
97
|
+
};
|
|
98
|
+
readonly checkoutRecovery: Record<Locale, CheckoutRecoveryEmailCopy>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CommerceEngagementEmailContent {
|
|
102
|
+
readonly backInStock: Record<Locale, BackInStockEmailCopy>;
|
|
103
|
+
readonly reorderReminder: Record<Locale, ReorderReminderEmailCopy>;
|
|
104
|
+
readonly reviewRequest: Record<Locale, ReviewRequestEmailCopy>;
|
|
105
|
+
readonly reviewEffects: Record<Locale, ReviewEffectsEmailCopy>;
|
|
106
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CommunicationPurpose } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export type CommunicationPurposeClassification = "marketing" | "non-marketing";
|
|
4
|
+
export interface CommunicationPurposeDefinition<Purpose extends string = CommunicationPurpose> {
|
|
5
|
+
readonly id: Purpose;
|
|
6
|
+
readonly classification: CommunicationPurposeClassification;
|
|
7
|
+
}
|
|
8
|
+
/** Immutable, explicitly composed identifiers; registration is never permission. */
|
|
9
|
+
export interface CommunicationPurposeRegistry<Purpose extends string = CommunicationPurpose> {
|
|
10
|
+
readonly purposes: readonly CommunicationPurposeDefinition<Purpose>[];
|
|
11
|
+
has(value: unknown): value is Purpose;
|
|
12
|
+
classification(purpose: Purpose): CommunicationPurposeClassification | undefined;
|
|
13
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export type Locale = "pl" | "en";
|
|
2
|
+
|
|
3
|
+
// Content blocks — the public contract a business domain (commerce,
|
|
4
|
+
// subscription, accounting, …) uses to compose an email body. A content module
|
|
5
|
+
// returns ordered blocks; the renderer turns them into branded HTML + text.
|
|
6
|
+
// Domains import ONLY these builders/types cross-domain (see the architecture
|
|
7
|
+
// guardrail whitelist for the `email` segment) — never render.ts internals.
|
|
8
|
+
//
|
|
9
|
+
// Builders are thin typed constructors: they keep call sites readable and let
|
|
10
|
+
// the block shape evolve without touching every content module.
|
|
11
|
+
|
|
12
|
+
export interface ParagraphBlock {
|
|
13
|
+
kind: "paragraph";
|
|
14
|
+
/**
|
|
15
|
+
* Plain text. A literal "\n" becomes a <br> in HTML and stays a newline in
|
|
16
|
+
* text. Two inline markers are honoured in HTML: `**bold**` and
|
|
17
|
+
* Markdown link syntax — see inlineHtml in render.ts for why only those two.
|
|
18
|
+
*/
|
|
19
|
+
text: string;
|
|
20
|
+
/** Render in the muted/secondary colour at a smaller size (fine print). */
|
|
21
|
+
muted?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Render BELOW the white content card, above the footer, as centred 11px
|
|
24
|
+
* fine print. For notices that must be delivered on a durable medium but must
|
|
25
|
+
* not compete with the message — the consumer-withdrawal paragraph on the
|
|
26
|
+
* paid-order mail, the marketing unsubscribe line. Ignored in the text part,
|
|
27
|
+
* where ordering already carries the same meaning.
|
|
28
|
+
*/
|
|
29
|
+
afterCard?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HeadingBlock {
|
|
33
|
+
kind: "heading";
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ListBlock {
|
|
38
|
+
kind: "list";
|
|
39
|
+
items: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AccentBoxBlock {
|
|
43
|
+
kind: "accentBox";
|
|
44
|
+
/** Optional uppercase eyebrow label in the brand colour. */
|
|
45
|
+
label?: string;
|
|
46
|
+
/** Body lines, rendered one per line. */
|
|
47
|
+
lines: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DataRow {
|
|
51
|
+
label: string;
|
|
52
|
+
value: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DataTableBlock {
|
|
56
|
+
kind: "dataTable";
|
|
57
|
+
/** Rendered inside an accent box as "label: <strong>value</strong>" lines. */
|
|
58
|
+
rows: DataRow[];
|
|
59
|
+
/** Optional text link rendered as the box's last line (a carrier tracking page next to its number). */
|
|
60
|
+
link?: { label: string; href: string };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface FeatureCardImage {
|
|
64
|
+
/** Absolute HTTPS URL of the illustration. */
|
|
65
|
+
src: string;
|
|
66
|
+
/** Blocked-image fallback, rendered in the card's accent colour. */
|
|
67
|
+
alt: string;
|
|
68
|
+
widthPx: number;
|
|
69
|
+
heightPx: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FeatureCardBlock {
|
|
73
|
+
kind: "featureCard";
|
|
74
|
+
/** Optional uppercase eyebrow above the title. */
|
|
75
|
+
eyebrow?: string;
|
|
76
|
+
title: string;
|
|
77
|
+
/** Body copy; the paragraph inline markers apply. */
|
|
78
|
+
text: string;
|
|
79
|
+
cta: { label: string; href: string };
|
|
80
|
+
/**
|
|
81
|
+
* Optional illustration. It sits beside the copy on a wide client and above
|
|
82
|
+
* it on a narrow one - a fluid layout with no media query, so it also holds
|
|
83
|
+
* in clients that drop <style>.
|
|
84
|
+
*/
|
|
85
|
+
image?: FeatureCardImage;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ButtonBlock {
|
|
89
|
+
kind: "button";
|
|
90
|
+
label: string;
|
|
91
|
+
href: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface LinkParagraphBlock {
|
|
95
|
+
kind: "linkParagraph";
|
|
96
|
+
/** Inline copy rendered as a single clickable <a href> inside a paragraph. */
|
|
97
|
+
text: string;
|
|
98
|
+
href: string;
|
|
99
|
+
/** Render in the muted/secondary colour at fine-print size (footer links). */
|
|
100
|
+
muted?: boolean;
|
|
101
|
+
/** See ParagraphBlock.afterCard. */
|
|
102
|
+
afterCard?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface DividerBlock {
|
|
106
|
+
kind: "divider";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type EmailBlock =
|
|
110
|
+
| ParagraphBlock
|
|
111
|
+
| HeadingBlock
|
|
112
|
+
| ListBlock
|
|
113
|
+
| AccentBoxBlock
|
|
114
|
+
| DataTableBlock
|
|
115
|
+
| FeatureCardBlock
|
|
116
|
+
| ButtonBlock
|
|
117
|
+
| LinkParagraphBlock
|
|
118
|
+
| DividerBlock;
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
export interface EmailTheme {
|
|
122
|
+
/** Outer page background (warm cream). */
|
|
123
|
+
bodyBg: string;
|
|
124
|
+
/** Content card background. */
|
|
125
|
+
cardBg: string;
|
|
126
|
+
/** Accent-box background (light teal tint). */
|
|
127
|
+
accentBg: string;
|
|
128
|
+
/** Accent-box / divider hairline. */
|
|
129
|
+
hairline: string;
|
|
130
|
+
/** Primary brand colour (teal): logo, links, buttons, accent border. */
|
|
131
|
+
brand: string;
|
|
132
|
+
/** Text colour on top of the brand colour (e.g. button label). */
|
|
133
|
+
onBrand: string;
|
|
134
|
+
/** Heading text. */
|
|
135
|
+
textHeading: string;
|
|
136
|
+
/** Body copy. */
|
|
137
|
+
textBody: string;
|
|
138
|
+
/** Muted/secondary text (footer, fine print). */
|
|
139
|
+
textMuted: string;
|
|
140
|
+
/** Font stack — system fonts only, for broad client support. */
|
|
141
|
+
fontFamily: string;
|
|
142
|
+
/** Text logo rendered in the header, and the alt/fallback shown when an image
|
|
143
|
+
* logo is set but blocked by the client. */
|
|
144
|
+
logoText: string;
|
|
145
|
+
/** Optional hosted logo image URL (absolute HTTPS). When set, the header
|
|
146
|
+
* renders an <img> (with logoText as the alt fallback) instead of the text
|
|
147
|
+
* wordmark. Left undefined by forks that prefer the text logo. */
|
|
148
|
+
logoImageUrl?: string;
|
|
149
|
+
/** Displayed logo height in px (width auto-scales). Used for the width/height
|
|
150
|
+
* attributes that keep layout stable when the image is blocked. */
|
|
151
|
+
logoImageHeightPx?: number;
|
|
152
|
+
/** Displayed logo width in px (paired with logoImageHeightPx for the attrs). */
|
|
153
|
+
logoImageWidthPx?: number;
|
|
154
|
+
/** Optional feature-card background; falls back to accentBg. Set it when the
|
|
155
|
+
* card's illustration carries its own background that must match. */
|
|
156
|
+
featureCardBg?: string;
|
|
157
|
+
/** Optional feature-card eyebrow and alt-text colour; falls back to brand. */
|
|
158
|
+
featureCardAccent?: string;
|
|
159
|
+
/** Content card corner radius, px. */
|
|
160
|
+
cardRadiusPx: number;
|
|
161
|
+
/** Button corner radius, px. */
|
|
162
|
+
buttonRadiusPx: number;
|
|
163
|
+
/** Max content width, px. */
|
|
164
|
+
maxWidthPx: number;
|
|
165
|
+
}
|
|
166
|
+
export interface EmailChromeStrings {
|
|
167
|
+
/** Optional footer tagline under the content card. Left undefined by brands
|
|
168
|
+
* that show only the legal fine print (see footerLegalLines). */
|
|
169
|
+
footerTagline?: string;
|
|
170
|
+
/** Accessible alt text for the logo (also the fallback when an image logo is
|
|
171
|
+
* blocked). */
|
|
172
|
+
logoAlt: string;
|
|
173
|
+
/** Optional hosted hero-banner image URL (absolute HTTPS), rendered full-width
|
|
174
|
+
* under the header. Each locale can carry different artwork. */
|
|
175
|
+
bannerImageUrl?: string;
|
|
176
|
+
/** Alt text for the banner image (shown when the image is blocked). */
|
|
177
|
+
bannerImageAlt?: string;
|
|
178
|
+
/** Optional legal fine-print lines rendered in the footer (company
|
|
179
|
+
* identification, e.g. art. 206 KSH). Each entry is one line. */
|
|
180
|
+
footerLegalLines?: string[];
|
|
181
|
+
}
|
|
182
|
+
export interface EmailBrand {
|
|
183
|
+
/** Brand label used in email copy when it intentionally differs from logo rendering. */
|
|
184
|
+
copyBrandName: string;
|
|
185
|
+
/**
|
|
186
|
+
* Softer, mixed-case brand label for in-body copy (e.g. "openlup"). Distinct from
|
|
187
|
+
* the all-caps chrome/sign-off form (copyBrandName, "OPENLUP"). Optional: callers
|
|
188
|
+
* fall back to copyBrandName when a brand does not define a cased variant.
|
|
189
|
+
*/
|
|
190
|
+
copyBrandNameCased?: string;
|
|
191
|
+
theme: EmailTheme;
|
|
192
|
+
chrome: Record<Locale, EmailChromeStrings>;
|
|
193
|
+
}
|
|
194
|
+
export interface RenderEmailInput {
|
|
195
|
+
/** Brand look + per-locale chrome, injected by the app layer (APP_EMAIL_BRAND). */
|
|
196
|
+
brand: EmailBrand;
|
|
197
|
+
locale: Locale;
|
|
198
|
+
subject: string;
|
|
199
|
+
/** Inbox preview text; hidden in the body. */
|
|
200
|
+
preheader?: string;
|
|
201
|
+
blocks: EmailBlock[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface RenderedEmail {
|
|
205
|
+
subject: string;
|
|
206
|
+
html: string;
|
|
207
|
+
text: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type EmailRenderer = (input: RenderEmailInput) => RenderedEmail;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// A guide card in a fulfillment mail: the selected copy pack names the guide by
|
|
2
|
+
// a site-relative path, and the sender knows the site origin, so this is the
|
|
3
|
+
// one place the two are joined into the absolute links a mail client needs.
|
|
4
|
+
|
|
5
|
+
import type { EmailGuideCardCopy } from "./commerceEmailContent.js";
|
|
6
|
+
import { featureCard, type FeatureCardBlock } from "./blocks.js";
|
|
7
|
+
|
|
8
|
+
// Exactly one slash at the seam, whichever side carries it.
|
|
9
|
+
function siteUrl(siteOrigin: string, path: string): string {
|
|
10
|
+
return `${siteOrigin.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function guideCardBlock(
|
|
14
|
+
copy: EmailGuideCardCopy,
|
|
15
|
+
siteOrigin: string,
|
|
16
|
+
contextName: string | null,
|
|
17
|
+
): FeatureCardBlock {
|
|
18
|
+
return featureCard({
|
|
19
|
+
eyebrow: copy.eyebrow,
|
|
20
|
+
title: copy.title,
|
|
21
|
+
text: copy.text(contextName),
|
|
22
|
+
cta: { label: copy.cta, href: siteUrl(siteOrigin, copy.path) },
|
|
23
|
+
...(copy.image
|
|
24
|
+
? {
|
|
25
|
+
image: {
|
|
26
|
+
src: siteUrl(siteOrigin, copy.image.path),
|
|
27
|
+
alt: copy.image.alt,
|
|
28
|
+
widthPx: copy.image.widthPx,
|
|
29
|
+
heightPx: copy.image.heightPx,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
});
|
|
34
|
+
}
|