@openlup/platform 0.15.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 +124 -0
- package/README.md +633 -6
- 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/dist/identity/adminAuthBinding.d.ts +28 -0
- package/dist/identity/adminAuthBinding.js +66 -0
- package/dist/identity/adminAuthorization.d.ts +34 -0
- package/dist/identity/adminAuthorization.js +72 -0
- package/dist/identity/adminContracts.d.ts +70 -0
- package/dist/identity/adminContracts.js +43 -0
- package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
- package/dist/identity/adminMagicLinkHandler.js +94 -0
- package/dist/identity/adminMeHandler.d.ts +15 -0
- package/dist/identity/adminMeHandler.js +49 -0
- package/dist/identity/adminPreflight.d.ts +16 -0
- package/dist/identity/adminPreflight.js +38 -0
- package/dist/identity/adminUserActionPorts.d.ts +31 -0
- package/dist/identity/adminUserActionPorts.js +51 -0
- package/dist/identity/adminUserHandlers.d.ts +38 -0
- package/dist/identity/adminUserHandlers.js +140 -0
- package/dist/identity/authVerifyUrl.d.ts +6 -0
- package/dist/identity/authVerifyUrl.js +54 -0
- package/dist/identity/contracts/contracts.d.ts +8 -0
- package/dist/identity/contracts/contracts.js +2 -0
- package/dist/identity/contracts/emailTrust.d.ts +3 -0
- package/dist/identity/contracts/emailTrust.js +4 -0
- package/dist/identity/contracts/index.d.ts +8 -0
- package/dist/identity/contracts/index.js +8 -0
- package/dist/identity/contracts/oidc.d.ts +3 -0
- package/dist/identity/contracts/oidc.js +9 -0
- package/dist/identity/contracts/operatorAuth.d.ts +35 -0
- package/dist/identity/contracts/operatorAuth.js +53 -0
- package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
- package/dist/identity/contracts/operatorInvitations.js +1 -0
- package/dist/identity/contracts/ports.d.ts +22 -0
- package/dist/identity/contracts/ports.js +1 -0
- package/dist/identity/contracts/privacy.d.ts +62 -0
- package/dist/identity/contracts/privacy.js +99 -0
- package/dist/identity/contracts/providerList.d.ts +3 -0
- package/dist/identity/contracts/providerList.js +11 -0
- package/dist/identity/contracts/types.d.ts +16 -0
- package/dist/identity/contracts/types.js +4 -0
- package/dist/identity/contribution.d.ts +8 -0
- package/dist/identity/contribution.js +22 -0
- package/dist/identity/customerAuth.d.ts +8 -0
- package/dist/identity/customerAuth.js +1 -0
- package/dist/identity/customerAuthorization.d.ts +5 -0
- package/dist/identity/customerAuthorization.js +22 -0
- package/dist/identity/customerContracts.d.ts +42 -0
- package/dist/identity/customerContracts.js +43 -0
- package/dist/identity/customerIdentityBinding.d.ts +44 -0
- package/dist/identity/customerIdentityBinding.js +60 -0
- package/dist/identity/customerIdentityVerifier.d.ts +8 -0
- package/dist/identity/customerIdentityVerifier.js +101 -0
- package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
- package/dist/identity/customerMagicLinkHandler.js +115 -0
- package/dist/identity/customerMeHandler.d.ts +11 -0
- package/dist/identity/customerMeHandler.js +46 -0
- package/dist/identity/customerPorts.d.ts +4 -0
- package/dist/identity/customerPorts.js +1 -0
- package/dist/identity/customerSessionBinding.d.ts +38 -0
- package/dist/identity/customerSessionBinding.js +80 -0
- package/dist/identity/customerSessionIssuer.d.ts +10 -0
- package/dist/identity/customerSessionIssuer.js +57 -0
- package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
- package/dist/identity/customerVerifyOtpHandler.js +41 -0
- package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
- package/dist/identity/directOperatorInvitationStore.js +193 -0
- package/dist/identity/emailTrustPolicy.d.ts +14 -0
- package/dist/identity/emailTrustPolicy.js +16 -0
- package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
- package/dist/identity/identityPersonalDataContributor.js +46 -0
- package/dist/identity/identityReadiness.d.ts +10 -0
- package/dist/identity/identityReadiness.js +25 -0
- package/dist/identity/index.d.ts +67 -0
- package/dist/identity/index.js +66 -0
- package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
- package/dist/identity/inviteAdminUserUseCase.js +75 -0
- package/dist/identity/jwtLibrary.d.ts +54 -0
- package/dist/identity/jwtLibrary.js +1 -0
- package/dist/identity/machineActorWriteFence.d.ts +4 -0
- package/dist/identity/machineActorWriteFence.js +13 -0
- package/dist/identity/magicLinkPorts.d.ts +28 -0
- package/dist/identity/magicLinkPorts.js +1 -0
- package/dist/identity/managedAccountLinkStore.d.ts +2 -0
- package/dist/identity/managedAccountLinkStore.js +93 -0
- package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
- package/dist/identity/managedAdminAuthVerifier.js +27 -0
- package/dist/identity/managedAdminUserGateways.d.ts +123 -0
- package/dist/identity/managedAdminUserGateways.js +127 -0
- package/dist/identity/managedClient.d.ts +90 -0
- package/dist/identity/managedClient.js +10 -0
- package/dist/identity/managedClientAuthLink.d.ts +3 -0
- package/dist/identity/managedClientAuthLink.js +115 -0
- package/dist/identity/managedCustomerMe.d.ts +13 -0
- package/dist/identity/managedCustomerMe.js +30 -0
- package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
- package/dist/identity/managedEmailTrustPolicy.js +105 -0
- package/dist/identity/managedIdentityVerifier.d.ts +2 -0
- package/dist/identity/managedIdentityVerifier.js +16 -0
- package/dist/identity/managedPrivacy.d.ts +4 -0
- package/dist/identity/managedPrivacy.js +39 -0
- package/dist/identity/messages.d.ts +29 -0
- package/dist/identity/messages.js +39 -0
- package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
- package/dist/identity/oidcIdentityVerifier.js +309 -0
- package/dist/identity/oidcPrincipal.d.ts +2 -0
- package/dist/identity/oidcPrincipal.js +10 -0
- package/dist/identity/operatorAdminAuth.d.ts +6 -0
- package/dist/identity/operatorAdminAuth.js +13 -0
- package/dist/identity/operatorAuthHandlers.d.ts +18 -0
- package/dist/identity/operatorAuthHandlers.js +71 -0
- package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
- package/dist/identity/operatorIdentityVerifier.js +19 -0
- package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
- package/dist/identity/operatorInvitationLifecycle.js +15 -0
- package/dist/identity/operatorInvitationPorts.d.ts +31 -0
- package/dist/identity/operatorInvitationPorts.js +1 -0
- package/dist/identity/operatorPorts.d.ts +37 -0
- package/dist/identity/operatorPorts.js +1 -0
- package/dist/identity/operatorSession.d.ts +13 -0
- package/dist/identity/operatorSession.js +31 -0
- package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
- package/dist/identity/operatorTokenAdminAuth.js +50 -0
- package/dist/identity/personalDataRegistry.d.ts +4 -0
- package/dist/identity/personalDataRegistry.js +11 -0
- package/dist/identity/platformAdminAuth.d.ts +15 -0
- package/dist/identity/platformAdminAuth.js +1 -0
- package/dist/identity/ports.d.ts +127 -0
- package/dist/identity/ports.js +38 -0
- package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
- package/dist/identity/postgresAccountLinkStore.js +52 -0
- package/dist/identity/postgresCustomerMe.d.ts +3 -0
- package/dist/identity/postgresCustomerMe.js +21 -0
- package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
- package/dist/identity/postgresCustomerRecovery.js +57 -0
- package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
- package/dist/identity/postgresOperatorIdentity.js +137 -0
- package/dist/identity/postgresPrivacy.d.ts +7 -0
- package/dist/identity/postgresPrivacy.js +126 -0
- package/dist/identity/privacyHandlers.d.ts +10 -0
- package/dist/identity/privacyHandlers.js +35 -0
- package/dist/identity/privacyPorts.d.ts +72 -0
- package/dist/identity/privacyPorts.js +8 -0
- package/dist/identity/privacyUseCases.d.ts +5 -0
- package/dist/identity/privacyUseCases.js +55 -0
- package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
- package/dist/identity/reconcileAccountForPrincipal.js +66 -0
- package/dist/identity/reconcileAccountHandler.d.ts +11 -0
- package/dist/identity/reconcileAccountHandler.js +30 -0
- package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
- package/dist/identity/removeAdminUserUseCase.js +60 -0
- package/dist/identity/response.d.ts +3 -0
- package/dist/identity/response.js +2 -0
- package/dist/identity/sql.d.ts +44 -0
- package/dist/identity/sql.js +10 -0
- package/package.json +22 -2
- package/release-gates.json +63 -8
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260810180001_self_host_actor_auth.sql +40 -0
- package/sql/20260810204057_customer_identity_read.sql +31 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
- package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
- package/sql/20261018120000_identity_operator_invitations.sql +42 -0
- package/sql/20261018180000_identity_operator_challenges.sql +56 -0
- package/sql/20261018190000_identity_privacy_erasures.sql +15 -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
- package/src/identity/adminAuthBinding.ts +76 -0
- package/src/identity/adminAuthorization.ts +106 -0
- package/src/identity/adminContracts.ts +52 -0
- package/src/identity/adminMagicLinkHandler.ts +132 -0
- package/src/identity/adminMeHandler.ts +64 -0
- package/src/identity/adminPreflight.ts +46 -0
- package/src/identity/adminUserActionPorts.ts +78 -0
- package/src/identity/adminUserHandlers.ts +174 -0
- package/src/identity/authVerifyUrl.ts +56 -0
- package/src/identity/contracts/contracts.ts +15 -0
- package/src/identity/contracts/emailTrust.ts +5 -0
- package/src/identity/contracts/index.ts +8 -0
- package/src/identity/contracts/oidc.ts +10 -0
- package/src/identity/contracts/operatorAuth.ts +61 -0
- package/src/identity/contracts/operatorInvitations.ts +32 -0
- package/src/identity/contracts/ports.ts +25 -0
- package/src/identity/contracts/privacy.ts +86 -0
- package/src/identity/contracts/providerList.ts +13 -0
- package/src/identity/contracts/types.ts +25 -0
- package/src/identity/contribution.ts +29 -0
- package/src/identity/customerAuth.ts +3 -0
- package/src/identity/customerAuthorization.ts +38 -0
- package/src/identity/customerContracts.ts +52 -0
- package/src/identity/customerIdentityBinding.ts +125 -0
- package/src/identity/customerIdentityVerifier.ts +141 -0
- package/src/identity/customerMagicLinkHandler.ts +171 -0
- package/src/identity/customerMeHandler.ts +64 -0
- package/src/identity/customerPorts.ts +2 -0
- package/src/identity/customerSessionBinding.ts +114 -0
- package/src/identity/customerSessionIssuer.ts +71 -0
- package/src/identity/customerVerifyOtpHandler.ts +44 -0
- package/src/identity/directOperatorInvitationStore.ts +178 -0
- package/src/identity/emailTrustPolicy.ts +16 -0
- package/src/identity/identityPersonalDataContributor.ts +48 -0
- package/src/identity/identityReadiness.ts +26 -0
- package/src/identity/index.ts +69 -0
- package/src/identity/inviteAdminUserUseCase.ts +134 -0
- package/src/identity/jwtLibrary.ts +24 -0
- package/src/identity/machineActorWriteFence.ts +19 -0
- package/src/identity/magicLinkPorts.ts +7 -0
- package/src/identity/managedAccountLinkStore.ts +105 -0
- package/src/identity/managedAdminAuthVerifier.ts +40 -0
- package/src/identity/managedAdminUserGateways.ts +164 -0
- package/src/identity/managedClient.ts +58 -0
- package/src/identity/managedClientAuthLink.ts +120 -0
- package/src/identity/managedCustomerMe.ts +52 -0
- package/src/identity/managedEmailTrustPolicy.ts +90 -0
- package/src/identity/managedIdentityVerifier.ts +20 -0
- package/src/identity/managedPrivacy.ts +37 -0
- package/src/identity/messages.ts +41 -0
- package/src/identity/oidcIdentityVerifier.ts +229 -0
- package/src/identity/oidcPrincipal.ts +10 -0
- package/src/identity/operatorAdminAuth.ts +14 -0
- package/src/identity/operatorAuthHandlers.ts +52 -0
- package/src/identity/operatorIdentityVerifier.ts +19 -0
- package/src/identity/operatorInvitationLifecycle.ts +14 -0
- package/src/identity/operatorInvitationPorts.ts +11 -0
- package/src/identity/operatorPorts.ts +11 -0
- package/src/identity/operatorSession.ts +30 -0
- package/src/identity/operatorTokenAdminAuth.ts +79 -0
- package/src/identity/personalDataRegistry.ts +8 -0
- package/src/identity/platformAdminAuth.ts +7 -0
- package/src/identity/ports.ts +158 -0
- package/src/identity/postgresAccountLinkStore.ts +49 -0
- package/src/identity/postgresCustomerMe.ts +33 -0
- package/src/identity/postgresCustomerRecovery.ts +72 -0
- package/src/identity/postgresOperatorIdentity.ts +123 -0
- package/src/identity/postgresPrivacy.ts +117 -0
- package/src/identity/privacyHandlers.ts +33 -0
- package/src/identity/privacyPorts.ts +52 -0
- package/src/identity/privacyUseCases.ts +48 -0
- package/src/identity/reconcileAccountForPrincipal.ts +99 -0
- package/src/identity/reconcileAccountHandler.ts +36 -0
- package/src/identity/removeAdminUserUseCase.ts +85 -0
- package/src/identity/response.ts +3 -0
- package/src/identity/sql.ts +10 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import type { SubscriptionRenewalUpcomingCopy } from "./subscriptionEmailContent.js";
|
|
4
|
+
import {
|
|
5
|
+
accentBox,
|
|
6
|
+
button,
|
|
7
|
+
heading,
|
|
8
|
+
paragraph,
|
|
9
|
+
type EmailBlock,
|
|
10
|
+
} from "./blocks.js";
|
|
11
|
+
|
|
12
|
+
export interface SubscriptionRenewalUpcomingEmailVars {
|
|
13
|
+
firstName: string | null;
|
|
14
|
+
/** Pre-formatted renewal CHARGE date (locale-formatted by the worker). */
|
|
15
|
+
renewalDateLabel: string | null;
|
|
16
|
+
/** Pre-formatted estimated delivery window, or null when unavailable. */
|
|
17
|
+
deliveryWindowLabel?: string | null;
|
|
18
|
+
/** Localized holiday caveat; present ONLY when a holiday moved the window. */
|
|
19
|
+
holidayNote?: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Pre-formatted composition edit deadline. Null when unknown OR when the
|
|
22
|
+
* deadline has already passed at send time — see the module header.
|
|
23
|
+
*/
|
|
24
|
+
editCutoffLabel?: string | null;
|
|
25
|
+
/** Formatted gross amount of the upcoming charge, e.g. "129,99 zł", or null. */
|
|
26
|
+
amountLabel: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Starter-pack acquisition stage of the delivery being announced, or null for
|
|
29
|
+
* an ordinary renewal. `"graduation"` means the NEXT delivery switches to the
|
|
30
|
+
* steady package — a size and rhythm change the customer agreed to at checkout
|
|
31
|
+
* but has not seen since, so it gets its own paragraph rather than a detail
|
|
32
|
+
* line. `"delivery2"` only marks the discounted repeat; its amount already
|
|
33
|
+
* rides on `amountLabel`.
|
|
34
|
+
*/
|
|
35
|
+
starterStage?: "delivery2" | "graduation" | null;
|
|
36
|
+
/** Units per delivery from the graduation on; renders only with `"graduation"`. */
|
|
37
|
+
starterSteadyUnitCount?: number | null;
|
|
38
|
+
/** Days between deliveries from the graduation on; renders only with `"graduation"`. */
|
|
39
|
+
starterSteadyCadenceDays?: number | null;
|
|
40
|
+
/** Adopter-formatted steady package, already localized; renders only with `"graduation"`. */
|
|
41
|
+
starterSteadyDetail?: string | null;
|
|
42
|
+
/** Absolute "manage subscription" URL; null/absent → no button. */
|
|
43
|
+
ctaUrl?: string | null;
|
|
44
|
+
/** Optional consumer context label; absent/null uses the selected pack's fallback. */
|
|
45
|
+
contextName?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SubscriptionRenewalUpcomingEmailContent {
|
|
49
|
+
subject: string;
|
|
50
|
+
preheader: string;
|
|
51
|
+
blocks: EmailBlock[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
55
|
+
export function createSubscriptionRenewalUpcomingContentFactory(input: {
|
|
56
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "renewalUpcoming">;
|
|
57
|
+
}) {
|
|
58
|
+
const { subscriptionEmailContent } = input;
|
|
59
|
+
|
|
60
|
+
function subscriptionRenewalUpcomingEmailContent(
|
|
61
|
+
locale: Locale,
|
|
62
|
+
vars: SubscriptionRenewalUpcomingEmailVars,
|
|
63
|
+
signoff: string,
|
|
64
|
+
): SubscriptionRenewalUpcomingEmailContent {
|
|
65
|
+
const copy: SubscriptionRenewalUpcomingCopy = subscriptionEmailContent.renewalUpcoming[locale];
|
|
66
|
+
const details: string[] = [
|
|
67
|
+
vars.renewalDateLabel ? copy.dateLine(vars.renewalDateLabel) : copy.noDateLine,
|
|
68
|
+
];
|
|
69
|
+
if (vars.deliveryWindowLabel) details.push(copy.deliveryLine(vars.deliveryWindowLabel));
|
|
70
|
+
if (vars.holidayNote) details.push(vars.holidayNote);
|
|
71
|
+
if (vars.amountLabel) details.push(copy.amountLine(vars.amountLabel));
|
|
72
|
+
// Null when already past — the caller, not this renderer, owns that decision.
|
|
73
|
+
if (vars.editCutoffLabel) details.push(copy.editCutoffLine(vars.editCutoffLabel));
|
|
74
|
+
|
|
75
|
+
const blocks: EmailBlock[] = [
|
|
76
|
+
heading(copy.heading),
|
|
77
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
78
|
+
paragraph(copy.intro(vars.contextName ?? null)),
|
|
79
|
+
accentBox(details, { label: copy.detailsLabel }),
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// The package-size change is the one thing in this email a customer could be
|
|
83
|
+
// surprised by, so it gets its own paragraph ABOVE the generic self-service
|
|
84
|
+
// line, and it names moving the delivery first.
|
|
85
|
+
if (vars.starterStage === "graduation" && vars.starterSteadyUnitCount && vars.starterSteadyCadenceDays) {
|
|
86
|
+
blocks.push(paragraph(copy.starterGraduationLine(vars.starterSteadyUnitCount, vars.starterSteadyCadenceDays, vars.starterSteadyDetail)));
|
|
87
|
+
}
|
|
88
|
+
blocks.push(paragraph(copy.manageLine));
|
|
89
|
+
|
|
90
|
+
if (vars.ctaUrl) {
|
|
91
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
blocks.push(paragraph(signoff));
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
subject: copy.subject,
|
|
98
|
+
preheader: copy.preheader,
|
|
99
|
+
blocks,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { subscriptionRenewalUpcomingEmailContent };
|
|
103
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.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 SubscriptionResumedEmailVars {
|
|
12
|
+
firstName: string | null;
|
|
13
|
+
/** Consumer-facing brand label injected by the app/composition root. */
|
|
14
|
+
brandName: string;
|
|
15
|
+
/** Absolute CTA URL (manage subscription); null/absent → no button. */
|
|
16
|
+
ctaUrl?: string | null;
|
|
17
|
+
/** Optional consumer context label; absent/null uses the selected pack's fallback. */
|
|
18
|
+
contextName?: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SubscriptionResumedEmailContent {
|
|
22
|
+
subject: string;
|
|
23
|
+
preheader: string;
|
|
24
|
+
blocks: EmailBlock[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
28
|
+
export function createSubscriptionResumedContentFactory(input: {
|
|
29
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "resumed">;
|
|
30
|
+
}) {
|
|
31
|
+
const { subscriptionEmailContent } = input;
|
|
32
|
+
|
|
33
|
+
function subscriptionResumedEmailContent(
|
|
34
|
+
locale: Locale,
|
|
35
|
+
vars: SubscriptionResumedEmailVars,
|
|
36
|
+
signoff: string,
|
|
37
|
+
): SubscriptionResumedEmailContent {
|
|
38
|
+
const copy = subscriptionEmailContent.resumed[locale];
|
|
39
|
+
const blocks: EmailBlock[] = [
|
|
40
|
+
heading(copy.heading),
|
|
41
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
42
|
+
paragraph(copy.intro(vars.contextName ?? null)),
|
|
43
|
+
accentBox(copy.reassurance(vars.brandName)),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (vars.ctaUrl) {
|
|
47
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
blocks.push(paragraph(copy.outro));
|
|
51
|
+
blocks.push(paragraph(signoff));
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
subject: copy.subject,
|
|
55
|
+
preheader: copy.preheader,
|
|
56
|
+
blocks,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { subscriptionResumedEmailContent };
|
|
60
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import type { SubscriptionWelcomeCopy } from "./subscriptionEmailContent.js";
|
|
4
|
+
import {
|
|
5
|
+
accentBox,
|
|
6
|
+
button,
|
|
7
|
+
heading,
|
|
8
|
+
paragraph,
|
|
9
|
+
type EmailBlock,
|
|
10
|
+
} from "./blocks.js";
|
|
11
|
+
|
|
12
|
+
export interface SubscriptionWelcomeEmailVars {
|
|
13
|
+
firstName: string | null;
|
|
14
|
+
cadenceDays: number | null;
|
|
15
|
+
/** Pre-formatted renewal CHARGE date (locale-formatted by the handler), or null. */
|
|
16
|
+
chargeDateLabel: string | null;
|
|
17
|
+
/** Pre-formatted estimated delivery window, e.g. "04.08.2026 – 05.08.2026", or null. */
|
|
18
|
+
deliveryWindowLabel: string | null;
|
|
19
|
+
/** Pre-formatted composition edit deadline, or null when unknown/already passed. */
|
|
20
|
+
editCutoffLabel?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Localized holiday caveat, supplied by the handler ONLY when a public holiday
|
|
23
|
+
* actually moved the estimated window. Null/absent is the normal case and must
|
|
24
|
+
* render nothing — a standing "we account for holidays" line is noise.
|
|
25
|
+
*/
|
|
26
|
+
holidayNote?: string | null;
|
|
27
|
+
/** Absolute "manage subscription" URL; null/absent → no button. */
|
|
28
|
+
ctaUrl?: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Starter-pack acquisition facts, present only for a subscription that carries
|
|
31
|
+
* an acquisition marker. `starterAmountLabel` is the DELIVERY-2 amount — the
|
|
32
|
+
* discounted repeat that lands on the date already stated above — and the
|
|
33
|
+
* steady pair describes what delivery 3 onwards looks like. The whole block is
|
|
34
|
+
* dropped when any of it is unknown; a half-stated plan is worse than none.
|
|
35
|
+
*/
|
|
36
|
+
starterAmountLabel?: string | null;
|
|
37
|
+
starterSteadyUnitCount?: number | null;
|
|
38
|
+
starterSteadyCadenceDays?: number | null;
|
|
39
|
+
/** Adopter-formatted steady package, already localized; the steady line's third argument. */
|
|
40
|
+
starterSteadyDetail?: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SubscriptionWelcomeEmailContent {
|
|
44
|
+
subject: string;
|
|
45
|
+
preheader: string;
|
|
46
|
+
blocks: EmailBlock[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
50
|
+
export function createSubscriptionWelcomeContentFactory(input: {
|
|
51
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "welcome">;
|
|
52
|
+
}) {
|
|
53
|
+
const { subscriptionEmailContent } = input;
|
|
54
|
+
|
|
55
|
+
function subscriptionWelcomeEmailContent(
|
|
56
|
+
locale: Locale,
|
|
57
|
+
vars: SubscriptionWelcomeEmailVars,
|
|
58
|
+
signoff: string,
|
|
59
|
+
): SubscriptionWelcomeEmailContent {
|
|
60
|
+
const copy: SubscriptionWelcomeCopy = subscriptionEmailContent.welcome[locale];
|
|
61
|
+
const details: string[] = [];
|
|
62
|
+
if (vars.cadenceDays && vars.cadenceDays > 0) details.push(copy.cadenceLine(vars.cadenceDays));
|
|
63
|
+
// Both labels are derived from the same `next_cycle_at`, so they are present or
|
|
64
|
+
// absent together; the fallback keeps the pre-existing "we'll confirm" promise.
|
|
65
|
+
details.push(
|
|
66
|
+
vars.chargeDateLabel && vars.deliveryWindowLabel
|
|
67
|
+
? copy.deliveryLine(vars.deliveryWindowLabel, vars.chargeDateLabel)
|
|
68
|
+
: copy.fallbackLine,
|
|
69
|
+
);
|
|
70
|
+
if (vars.holidayNote) details.push(vars.holidayNote);
|
|
71
|
+
// Acquisition plan: the delivery-2 amount and the steady rhythm, stated once at
|
|
72
|
+
// the only moment the customer is actually reading about their plan.
|
|
73
|
+
if (vars.starterAmountLabel) details.push(copy.starterDelivery2Line(vars.starterAmountLabel));
|
|
74
|
+
if (vars.starterSteadyUnitCount && vars.starterSteadyCadenceDays) {
|
|
75
|
+
details.push(copy.starterSteadyLine(vars.starterSteadyUnitCount, vars.starterSteadyCadenceDays, vars.starterSteadyDetail));
|
|
76
|
+
}
|
|
77
|
+
if (vars.editCutoffLabel) details.push(copy.editCutoffLine(vars.editCutoffLabel));
|
|
78
|
+
|
|
79
|
+
const blocks: EmailBlock[] = [
|
|
80
|
+
heading(copy.heading),
|
|
81
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
82
|
+
paragraph(copy.intro),
|
|
83
|
+
accentBox(details, { label: copy.detailsLabel }),
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
blocks.push(paragraph(copy.manageLine));
|
|
87
|
+
|
|
88
|
+
if (vars.ctaUrl) {
|
|
89
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
blocks.push(paragraph(copy.outro));
|
|
93
|
+
blocks.push(paragraph(signoff));
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
subject: copy.subject,
|
|
97
|
+
preheader: copy.preheader,
|
|
98
|
+
blocks,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { subscriptionWelcomeEmailContent };
|
|
102
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import type { SubscriptionWinbackCopy } from "./subscriptionEmailContent.js";
|
|
4
|
+
import {
|
|
5
|
+
accentBox,
|
|
6
|
+
button,
|
|
7
|
+
heading,
|
|
8
|
+
paragraph,
|
|
9
|
+
type EmailBlock,
|
|
10
|
+
} from "./blocks.js";
|
|
11
|
+
|
|
12
|
+
export interface SubscriptionWinbackEmailVars {
|
|
13
|
+
firstName: string | null;
|
|
14
|
+
/** Consumer-facing brand label injected by the app/composition root. */
|
|
15
|
+
brandName: string;
|
|
16
|
+
/** Absolute CTA URL (the customer account / "Wznów" button); null/absent → no button. */
|
|
17
|
+
ctaUrl?: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SubscriptionWinbackEmailContent {
|
|
21
|
+
subject: string;
|
|
22
|
+
preheader: string;
|
|
23
|
+
blocks: EmailBlock[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
27
|
+
export function createSubscriptionWinbackContentFactory(input: {
|
|
28
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "winback">;
|
|
29
|
+
}) {
|
|
30
|
+
const { subscriptionEmailContent } = input;
|
|
31
|
+
|
|
32
|
+
function subscriptionWinbackEmailContent(
|
|
33
|
+
locale: Locale,
|
|
34
|
+
vars: SubscriptionWinbackEmailVars,
|
|
35
|
+
signoff: string,
|
|
36
|
+
): SubscriptionWinbackEmailContent {
|
|
37
|
+
const copy: SubscriptionWinbackCopy = subscriptionEmailContent.winback[locale];
|
|
38
|
+
const blocks: EmailBlock[] = [
|
|
39
|
+
heading(copy.heading),
|
|
40
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
41
|
+
paragraph(copy.intro),
|
|
42
|
+
];
|
|
43
|
+
if (copy.reasonsLabel) {
|
|
44
|
+
blocks.push(paragraph(copy.reasonsLabel(vars.brandName)));
|
|
45
|
+
}
|
|
46
|
+
blocks.push(accentBox(copy.reasons));
|
|
47
|
+
|
|
48
|
+
if (vars.ctaUrl) {
|
|
49
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
blocks.push(paragraph(copy.outro));
|
|
53
|
+
blocks.push(paragraph(signoff));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
subject: copy.subject,
|
|
57
|
+
preheader: copy.preheader,
|
|
58
|
+
blocks,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { subscriptionWinbackEmailContent };
|
|
62
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface TransactionalDeliveryCommand {
|
|
2
|
+
readonly idempotencyKey: string;
|
|
3
|
+
/** Opaque recipient reference; never persisted outside the command fingerprint. */
|
|
4
|
+
readonly recipientReference: string;
|
|
5
|
+
/** Opaque template reference; never persisted outside the command fingerprint. */
|
|
6
|
+
readonly templateReference: string;
|
|
7
|
+
/** Optional bounded reference to an expiring/revocable access grant. */
|
|
8
|
+
readonly accessGrantReference?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TransactionalDeliveryReceipt = {
|
|
12
|
+
readonly idempotencyKey: string;
|
|
13
|
+
readonly commandFingerprint: string;
|
|
14
|
+
readonly state: "accepted";
|
|
15
|
+
readonly deliveryReference: string;
|
|
16
|
+
readonly errorCode: null;
|
|
17
|
+
readonly attemptCount: number;
|
|
18
|
+
} | {
|
|
19
|
+
readonly idempotencyKey: string;
|
|
20
|
+
readonly commandFingerprint: string;
|
|
21
|
+
readonly state: "failed";
|
|
22
|
+
readonly deliveryReference: null;
|
|
23
|
+
readonly errorCode: string;
|
|
24
|
+
readonly attemptCount: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface TransactionalDeliveryReceiptStore {
|
|
28
|
+
readReceipt(idempotencyKey: string): Promise<TransactionalDeliveryReceipt | null>;
|
|
29
|
+
recordAccepted(input: {
|
|
30
|
+
idempotencyKey: string;
|
|
31
|
+
commandFingerprint: string;
|
|
32
|
+
deliveryReference: string;
|
|
33
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
34
|
+
recordFailed(input: {
|
|
35
|
+
idempotencyKey: string;
|
|
36
|
+
commandFingerprint: string;
|
|
37
|
+
errorCode: string;
|
|
38
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CapturedTransactionalDeliveryPort {
|
|
42
|
+
deliver(command: TransactionalDeliveryCommand): Promise<{ deliveryReference: string }>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
type COMMUNICATION_CHANNEL = "email";
|
|
2
|
+
|
|
3
|
+
type EMAIL_SEND_STATUSES = "queued" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "skipped";
|
|
4
|
+
|
|
5
|
+
type EMAIL_SEND_SKIP_REASONS = "sequence_paused" | "already_sent" | "template_unavailable" | "race_lost" | "policy_blocked" | "admin_disabled";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export type CommunicationChannel = COMMUNICATION_CHANNEL;
|
|
9
|
+
/** The host supplies its persisted provider vocabulary. */
|
|
10
|
+
export type CommunicationProvider<Provider extends string = string> = Provider;
|
|
11
|
+
export type EmailSendStatus = EMAIL_SEND_STATUSES;
|
|
12
|
+
export type EmailSendSkipReason = EMAIL_SEND_SKIP_REASONS;
|
|
13
|
+
|
|
14
|
+
type COMMUNICATION_PURPOSES = "transactional" | "tester_program" | "marketing_launch_offer" | "marketing_newsletter" | "subscription_dunning" | "admin_notification";
|
|
15
|
+
|
|
16
|
+
type COMMUNICATION_PERMISSION_STATES = "unknown" | "granted" | "denied" | "suppressed";
|
|
17
|
+
|
|
18
|
+
type COMMUNICATION_POLICY_DECISIONS = "allowed" | "blocked";
|
|
19
|
+
|
|
20
|
+
export type CommunicationPurpose = COMMUNICATION_PURPOSES;
|
|
21
|
+
export type CommunicationPermissionState = COMMUNICATION_PERMISSION_STATES;
|
|
22
|
+
export type CommunicationPolicyDecision = COMMUNICATION_POLICY_DECISIONS;
|
|
23
|
+
|
|
24
|
+
export interface CommunicationRecipientRef {
|
|
25
|
+
system: "supabase";
|
|
26
|
+
table: "testers";
|
|
27
|
+
id: string;
|
|
28
|
+
email: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface EmailTemplateRef {
|
|
32
|
+
slug: string;
|
|
33
|
+
locale: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface EmailSendDedupeKeyInput {
|
|
37
|
+
recipientId: string;
|
|
38
|
+
templateSlug: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type EmailSendDecision =
|
|
42
|
+
| { outcome: "send" }
|
|
43
|
+
| {
|
|
44
|
+
outcome: "skip";
|
|
45
|
+
reason: EmailSendSkipReason;
|
|
46
|
+
existingMessageId: string | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export interface EmailMessageReadModel<Provider extends string = string> {
|
|
50
|
+
id: string;
|
|
51
|
+
channel: CommunicationChannel;
|
|
52
|
+
recipientId: string;
|
|
53
|
+
templateSlug: string;
|
|
54
|
+
status: EmailSendStatus;
|
|
55
|
+
provider: CommunicationProvider<Provider> | null;
|
|
56
|
+
providerMessageId: string | null;
|
|
57
|
+
skippedReason: EmailSendSkipReason | null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
|
|
3
|
+
/** Host-selected schema inventory: managed and direct installations have different objects. */
|
|
4
|
+
export function createCommunicationsContribution(options: {
|
|
5
|
+
version: string;
|
|
6
|
+
requiredSchema: RequiredSchema;
|
|
7
|
+
routes?: Contribution["routes"];
|
|
8
|
+
handlers?: Contribution["handlers"];
|
|
9
|
+
schedules?: Contribution["schedules"];
|
|
10
|
+
requiredPorts?: readonly string[];
|
|
11
|
+
}): Contribution {
|
|
12
|
+
const routes = options.routes ?? [];
|
|
13
|
+
const handlers = options.handlers ?? [];
|
|
14
|
+
const schedules = options.schedules ?? [];
|
|
15
|
+
return {
|
|
16
|
+
manifest: {
|
|
17
|
+
name: "@openlup/platform", version: options.version, kind: "rail",
|
|
18
|
+
module: {
|
|
19
|
+
id: "communications", kind: "capability", provides: ["./communications", "./communications/contracts"],
|
|
20
|
+
dependsOn: [
|
|
21
|
+
{ module: "data", entries: ["./data"] },
|
|
22
|
+
{ module: "identity", entries: ["./identity"] },
|
|
23
|
+
{ module: "http", entries: ["./http"] },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
requiredSchema: options.requiredSchema,
|
|
27
|
+
emits: [], handles: handlers.map((handler) => handler.eventType),
|
|
28
|
+
schedules: schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })), routes: routes.map((route) => route.id),
|
|
29
|
+
requiredPorts: [...new Set(options.requiredPorts ?? [])], env: [],
|
|
30
|
+
},
|
|
31
|
+
handlers, schedules, routes,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export interface DeliveryPollCandidate {
|
|
2
|
+
id: string;
|
|
3
|
+
messageId: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface DeliveryStatusRead {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
httpStatus: number;
|
|
9
|
+
recipientEmail: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The host maps its protocol to an opaque event type and terminal classification. */
|
|
13
|
+
export interface DeliveryPollObservation {
|
|
14
|
+
eventType: string;
|
|
15
|
+
terminal: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DeliveryReconciliationPort {
|
|
19
|
+
findFrozenSends(input: { now: string; graceMinutes: number; limit: number }): Promise<DeliveryPollCandidate[]>;
|
|
20
|
+
applyPolledEvent(input: {
|
|
21
|
+
sendId: string;
|
|
22
|
+
messageId: string;
|
|
23
|
+
eventType: string;
|
|
24
|
+
eventAt: string;
|
|
25
|
+
recipientEmail: string | null;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
recordPollAttempt(input: { sendId: string; attemptedAt: string }): Promise<void>;
|
|
28
|
+
recordSendNotFound(input: { sendId: string; threshold: number }): Promise<{ abandoned: boolean }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DELIVERY_POLL_ABANDON_AFTER_NOT_FOUND = 3;
|
|
32
|
+
|
|
33
|
+
export interface DeliveryReadFailureBreakdown {
|
|
34
|
+
notFound: number;
|
|
35
|
+
authFailed: number;
|
|
36
|
+
network: number;
|
|
37
|
+
providerError: number;
|
|
38
|
+
other: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DeliveryReconciliationResult {
|
|
42
|
+
checked: number;
|
|
43
|
+
reconciled: number;
|
|
44
|
+
stillPending: number;
|
|
45
|
+
terminalPolled: number;
|
|
46
|
+
failures: number;
|
|
47
|
+
abandoned: number;
|
|
48
|
+
failureBreakdown: DeliveryReadFailureBreakdown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function classifyReadFailure(breakdown: DeliveryReadFailureBreakdown, httpStatus: number): void {
|
|
52
|
+
if (httpStatus === 404) breakdown.notFound += 1;
|
|
53
|
+
else if (httpStatus === 401 || httpStatus === 403) breakdown.authFailed += 1;
|
|
54
|
+
else if (httpStatus === 0) breakdown.network += 1;
|
|
55
|
+
else if (httpStatus >= 500 && httpStatus <= 599) breakdown.providerError += 1;
|
|
56
|
+
else breakdown.other += 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Sequential bounded polling around one host-owned atomic convergence writer. */
|
|
60
|
+
export async function runDeliveryReconciliation<Read extends DeliveryStatusRead>(input: {
|
|
61
|
+
port: DeliveryReconciliationPort;
|
|
62
|
+
readEmail: (messageId: string) => Promise<Read>;
|
|
63
|
+
observationFor: (read: Read) => DeliveryPollObservation | null;
|
|
64
|
+
now: string;
|
|
65
|
+
graceMinutes: number;
|
|
66
|
+
limit: number;
|
|
67
|
+
}): Promise<DeliveryReconciliationResult> {
|
|
68
|
+
const result: DeliveryReconciliationResult = {
|
|
69
|
+
checked: 0, reconciled: 0, stillPending: 0, terminalPolled: 0, failures: 0, abandoned: 0,
|
|
70
|
+
failureBreakdown: { notFound: 0, authFailed: 0, network: 0, providerError: 0, other: 0 },
|
|
71
|
+
};
|
|
72
|
+
const frozen = await input.port.findFrozenSends({ now: input.now, graceMinutes: input.graceMinutes, limit: input.limit });
|
|
73
|
+
for (const send of frozen) {
|
|
74
|
+
result.checked += 1;
|
|
75
|
+
const read = await input.readEmail(send.messageId);
|
|
76
|
+
if (read.httpStatus !== 404) {
|
|
77
|
+
try {
|
|
78
|
+
await input.port.recordPollAttempt({ sendId: send.id, attemptedAt: input.now });
|
|
79
|
+
} catch {
|
|
80
|
+
// Fairness bookkeeping failure must not discard observed delivery truth.
|
|
81
|
+
result.failures += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!read.ok) {
|
|
85
|
+
result.failures += 1;
|
|
86
|
+
classifyReadFailure(result.failureBreakdown, read.httpStatus);
|
|
87
|
+
if (read.httpStatus === 404) {
|
|
88
|
+
const { abandoned } = await input.port.recordSendNotFound({ sendId: send.id, threshold: DELIVERY_POLL_ABANDON_AFTER_NOT_FOUND });
|
|
89
|
+
if (abandoned) result.abandoned += 1;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const observation = input.observationFor(read);
|
|
94
|
+
if (!observation) {
|
|
95
|
+
result.stillPending += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await input.port.applyPolledEvent({ sendId: send.id, messageId: send.messageId,
|
|
100
|
+
eventType: observation.eventType, eventAt: input.now, recipientEmail: read.recipientEmail });
|
|
101
|
+
result.reconciled += 1;
|
|
102
|
+
if (observation.terminal) result.terminalPolled += 1;
|
|
103
|
+
} catch {
|
|
104
|
+
result.failures += 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { AuthEmailTransportPort, AuthEmailTransportResult } from "./contracts/authEmail.js";
|
|
2
|
+
import type { EmailMessage, EmailSendResult, EmailTransport } from "./emailTransport.js";
|
|
3
|
+
|
|
4
|
+
export interface EmailEgressContext {
|
|
5
|
+
mode: "production" | "sandbox_direct" | "sandbox_sink";
|
|
6
|
+
reason: string;
|
|
7
|
+
intendedRecipient: string;
|
|
8
|
+
recipient: string;
|
|
9
|
+
/** Selected by the host; consumed only by its concrete sender, never logged. */
|
|
10
|
+
credential: string;
|
|
11
|
+
environmentTag: "production" | "non_production";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type EmailEgressDecision = EmailEgressContext | { mode: "drop"; reason: string } | { mode: "blocked"; reason: string };
|
|
15
|
+
export type EmailEgressPolicy = (intendedRecipient: string) => EmailEgressDecision | Promise<EmailEgressDecision>;
|
|
16
|
+
|
|
17
|
+
const admitted = new WeakSet<EmailTransport>();
|
|
18
|
+
const admittedAuth = new WeakSet<AuthEmailTransportPort>();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Host composition bridge for an implementation that already enforces egress
|
|
22
|
+
* internally. This is a trust boundary: its sender must itself refuse unknown
|
|
23
|
+
* policy before effects. New network adapters should use createGuardedEmailTransport.
|
|
24
|
+
* Neither a provider name nor an object property establishes admission.
|
|
25
|
+
*/
|
|
26
|
+
export function admitPolicyEnforcedEmailTransport<T extends EmailTransport>(transport: T): T {
|
|
27
|
+
admitted.add(transport);
|
|
28
|
+
return transport;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Mandatory boundary for supported injected transports; raw objects fail closed. */
|
|
32
|
+
export function guardEmailTransport(transport: EmailTransport): EmailTransport {
|
|
33
|
+
if (admitted.has(transport)) return transport;
|
|
34
|
+
return { providerKind: transport.providerKind, send: async () => refused("email_egress_unadmitted") };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Construct a network adapter whose sender sees only a validated host decision. */
|
|
38
|
+
export function createGuardedEmailTransport(input: {
|
|
39
|
+
providerKind: string;
|
|
40
|
+
policy: EmailEgressPolicy;
|
|
41
|
+
send: (message: EmailMessage, context: Readonly<EmailEgressContext>) => Promise<EmailSendResult>;
|
|
42
|
+
}): EmailTransport {
|
|
43
|
+
return admitPolicyEnforcedEmailTransport({
|
|
44
|
+
providerKind: input.providerKind,
|
|
45
|
+
async send(message) {
|
|
46
|
+
let decision: unknown;
|
|
47
|
+
try { decision = await input.policy(message.to); }
|
|
48
|
+
catch { return refused("email_egress_policy_unavailable"); }
|
|
49
|
+
if (!validDecision(decision, message.to)) return refused("email_egress_policy_invalid");
|
|
50
|
+
if (decision.mode === "blocked") return refused("email_egress_blocked", decision.reason);
|
|
51
|
+
if (decision.mode === "drop") return {
|
|
52
|
+
outcome: { ok: true, resendId: null, httpStatus: 0, providerError: null, aborted: false,
|
|
53
|
+
suppressed: "drop", egressMode: "drop", egressReason: decision.reason },
|
|
54
|
+
providerResponse: { suppressed: "drop", egress_reason: decision.reason },
|
|
55
|
+
};
|
|
56
|
+
const context = Object.freeze({ ...decision });
|
|
57
|
+
const result = await input.send({ ...message, to: context.recipient }, context);
|
|
58
|
+
return { ...result, outcome: { ...result.outcome, egressMode: context.mode, egressReason: context.reason } };
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validDecision(value: unknown, intended: string): value is EmailEgressDecision {
|
|
64
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
65
|
+
const d = value as Record<string, unknown>;
|
|
66
|
+
// Policy diagnostics are bounded codes, never arbitrary exception/secret text.
|
|
67
|
+
if (typeof d.reason !== "string" || !/^[a-z][a-z0-9_]{0,79}$/.test(d.reason)) return false;
|
|
68
|
+
if (d.mode === "drop" || d.mode === "blocked") return Object.keys(d).every((key) => key === "mode" || key === "reason");
|
|
69
|
+
if (d.mode !== "production" && d.mode !== "sandbox_direct" && d.mode !== "sandbox_sink") return false;
|
|
70
|
+
if (!Object.keys(d).every((key) => ["mode", "reason", "intendedRecipient", "recipient", "credential", "environmentTag"].includes(key))) return false;
|
|
71
|
+
if (!mailbox(intended) || d.intendedRecipient !== intended || !mailbox(d.recipient)) return false;
|
|
72
|
+
if (d.mode !== "sandbox_sink" && d.recipient !== intended) return false;
|
|
73
|
+
if (typeof d.credential !== "string" || !d.credential.trim()) return false;
|
|
74
|
+
return d.environmentTag === (d.mode === "production" ? "production" : "non_production");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mailbox(value: unknown): value is string {
|
|
78
|
+
return typeof value === "string" && /^[^\s@,;<>]+@[^\s@,;<>]+\.[^\s@,;<>]+$/.test(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function refused(code: string, reason?: string): EmailSendResult {
|
|
82
|
+
return { outcome: { ok: false, resendId: null, httpStatus: 0, providerError: code, aborted: false,
|
|
83
|
+
...(reason ? { egressReason: reason } : {}) }, providerResponse: { egress_reason: code } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Preserve the Auth envelope/result mapping while retaining package admission. */
|
|
87
|
+
export function createAuthEmailTransport(transport: EmailTransport): AuthEmailTransportPort {
|
|
88
|
+
const guarded = guardEmailTransport(transport);
|
|
89
|
+
const auth: AuthEmailTransportPort = {
|
|
90
|
+
providerKind: transport.providerKind,
|
|
91
|
+
async send(message) {
|
|
92
|
+
const result = await guarded.send({ from: message.sender, to: message.recipient,
|
|
93
|
+
subject: message.subject, html: message.html, text: message.text });
|
|
94
|
+
return { outcome: { ...result.outcome, messageId: result.outcome.resendId }, providerResponse: result.providerResponse };
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
admittedAuth.add(auth);
|
|
98
|
+
return auth;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Raw Auth closures must compose through the admitted email transport bridge. */
|
|
102
|
+
export function guardAuthEmailTransport(transport: AuthEmailTransportPort): AuthEmailTransportPort {
|
|
103
|
+
if (admittedAuth.has(transport)) return transport;
|
|
104
|
+
return {
|
|
105
|
+
providerKind: transport.providerKind,
|
|
106
|
+
async send(): Promise<AuthEmailTransportResult> {
|
|
107
|
+
const result = refused("email_egress_unadmitted");
|
|
108
|
+
return { outcome: { ...result.outcome, messageId: null }, providerResponse: result.providerResponse };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|