@omega.js/backend 0.1.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.
Files changed (993) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +1053 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-backend +2 -0
  5. package/bin/omg +2 -0
  6. package/cli.js +3 -0
  7. package/dist/cli/command-table.js +268 -0
  8. package/dist/cli/commands/auth.js +259 -0
  9. package/dist/cli/commands/base-command.js +451 -0
  10. package/dist/cli/commands/build.js +27 -0
  11. package/dist/cli/commands/clean.js +13 -0
  12. package/dist/cli/commands/clear.js +11 -0
  13. package/dist/cli/commands/cwd.js +9 -0
  14. package/dist/cli/commands/deploy.js +205 -0
  15. package/dist/cli/commands/emulator-orphans.js +440 -0
  16. package/dist/cli/commands/emulator.js +1829 -0
  17. package/dist/cli/commands/firebase-init.js +101 -0
  18. package/dist/cli/commands/firestore.js +261 -0
  19. package/dist/cli/commands/indexes.js +51 -0
  20. package/dist/cli/commands/install.js +151 -0
  21. package/dist/cli/commands/logs.js +358 -0
  22. package/dist/cli/commands/mcp.js +41 -0
  23. package/dist/cli/commands/migrate-markers.js +176 -0
  24. package/dist/cli/commands/migrate-rules.js +90 -0
  25. package/dist/cli/commands/migrate.js +59 -0
  26. package/dist/cli/commands/serve.js +208 -0
  27. package/dist/cli/commands/setup-tests/base-test.js +174 -0
  28. package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
  29. package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
  30. package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
  31. package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
  32. package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
  33. package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
  34. package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
  35. package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
  36. package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
  37. package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
  38. package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
  39. package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
  40. package/dist/cli/commands/setup-tests/functions-package.js +44 -0
  41. package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
  42. package/dist/cli/commands/setup-tests/gitignore.js +122 -0
  43. package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
  44. package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
  45. package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
  46. package/dist/cli/commands/setup-tests/helpers.js +72 -0
  47. package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
  48. package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
  49. package/dist/cli/commands/setup-tests/index.js +111 -0
  50. package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
  51. package/dist/cli/commands/setup-tests/java-installed.js +27 -0
  52. package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
  53. package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
  54. package/dist/cli/commands/setup-tests/node-version.js +60 -0
  55. package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
  56. package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
  57. package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
  58. package/dist/cli/commands/setup-tests/omega-config.js +84 -0
  59. package/dist/cli/commands/setup-tests/project-directories.js +34 -0
  60. package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
  61. package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
  62. package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
  63. package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
  64. package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
  65. package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
  66. package/dist/cli/commands/setup-tests/service-account.js +86 -0
  67. package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
  68. package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
  69. package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
  70. package/dist/cli/commands/stripe.js +14 -0
  71. package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
  72. package/dist/cli/commands/test.js +976 -0
  73. package/dist/cli/commands/update.js +27 -0
  74. package/dist/cli/commands/version.js +10 -0
  75. package/dist/cli/commands/watch.js +196 -0
  76. package/dist/cli/flags.js +13 -0
  77. package/dist/cli/index.js +173 -0
  78. package/dist/cli/run.js +33 -0
  79. package/dist/cli/utils/attach-log-file.js +4 -0
  80. package/dist/cli/utils/compile-rules.js +1155 -0
  81. package/dist/cli/utils/ensure-target.js +314 -0
  82. package/dist/cli/utils/project-type.js +140 -0
  83. package/dist/cli/utils/public-files.js +49 -0
  84. package/dist/cli/utils/safe-install.js +4 -0
  85. package/dist/cli/utils/spawn-shell.js +94 -0
  86. package/dist/cli/utils/stage-functions.js +338 -0
  87. package/dist/cli/utils/stage-local-packages.js +210 -0
  88. package/dist/cli/utils/target-checks.js +161 -0
  89. package/dist/cli/utils/target.js +24 -0
  90. package/dist/cli/utils/ui.js +292 -0
  91. package/dist/defaults/AGENTS.md +125 -0
  92. package/dist/defaults/CHANGELOG.md +15 -0
  93. package/dist/defaults/CLAUDE.md +1 -0
  94. package/dist/defaults/_.gitignore +65 -0
  95. package/dist/defaults/docs/README.md +17 -0
  96. package/dist/defaults/test/README.md +65 -0
  97. package/dist/defaults/test/_init.js +14 -0
  98. package/dist/defaults/test/helpers/connect-trap.js +72 -0
  99. package/dist/defaults/test/unit/registration.test.js +143 -0
  100. package/dist/defaults/test/unit/rules-posture.test.js +101 -0
  101. package/dist/defaults/test/unit/socket-free.test.js +43 -0
  102. package/dist/manager/events/auth/before-create.js +89 -0
  103. package/dist/manager/events/auth/before-signin.js +76 -0
  104. package/dist/manager/events/auth/on-create.js +112 -0
  105. package/dist/manager/events/auth/on-delete.js +141 -0
  106. package/dist/manager/events/auth/utils.js +93 -0
  107. package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
  108. package/dist/manager/events/cron/daily/data-requests.js +57 -0
  109. package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
  110. package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
  111. package/dist/manager/events/cron/daily/reset-usage.js +188 -0
  112. package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
  113. package/dist/manager/events/cron/daily.js +3 -0
  114. package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
  115. package/dist/manager/events/cron/frequent/email-queue.js +71 -0
  116. package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
  117. package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
  118. package/dist/manager/events/cron/frequent.js +3 -0
  119. package/dist/manager/events/cron/runner.js +64 -0
  120. package/dist/manager/events/firestore/notifications/on-write.js +123 -0
  121. package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
  122. package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
  123. package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
  124. package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
  125. package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
  126. package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
  127. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
  128. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
  129. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
  130. package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
  131. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
  132. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
  133. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
  134. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
  135. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
  136. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
  137. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
  138. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
  139. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
  140. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
  141. package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
  142. package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
  143. package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
  144. package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
  145. package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
  146. package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
  147. package/dist/manager/functions/_legacy/admin/query.js +198 -0
  148. package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
  149. package/dist/manager/functions/_legacy/template.js +33 -0
  150. package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
  151. package/dist/manager/functions/_legacy/test/webhook.js +37 -0
  152. package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
  153. package/dist/manager/helpers/analytics.js +498 -0
  154. package/dist/manager/helpers/api-manager.js +313 -0
  155. package/dist/manager/helpers/backend-router.js +37 -0
  156. package/dist/manager/helpers/context/authenticate.js +215 -0
  157. package/dist/manager/helpers/context/client-info.js +91 -0
  158. package/dist/manager/helpers/context/index.js +228 -0
  159. package/dist/manager/helpers/context/logging.js +150 -0
  160. package/dist/manager/helpers/context/parse.js +160 -0
  161. package/dist/manager/helpers/context/respond.js +247 -0
  162. package/dist/manager/helpers/event-middleware.js +109 -0
  163. package/dist/manager/helpers/metadata.js +32 -0
  164. package/dist/manager/helpers/middleware.js +541 -0
  165. package/dist/manager/helpers/redact-secret.js +25 -0
  166. package/dist/manager/helpers/resolved-config.js +34 -0
  167. package/dist/manager/helpers/roles.js +69 -0
  168. package/dist/manager/helpers/safe-compare.js +31 -0
  169. package/dist/manager/helpers/schema-engine.js +314 -0
  170. package/dist/manager/helpers/schema-zod.js +203 -0
  171. package/dist/manager/helpers/settings.js +215 -0
  172. package/dist/manager/helpers/usage.js +541 -0
  173. package/dist/manager/helpers/user.js +57 -0
  174. package/dist/manager/helpers/utilities.js +565 -0
  175. package/dist/manager/index.js +1404 -0
  176. package/dist/manager/libraries/abandoned-cart-config.js +12 -0
  177. package/dist/manager/libraries/ai/index.js +321 -0
  178. package/dist/manager/libraries/ai/prompt.js +85 -0
  179. package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
  180. package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
  181. package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
  182. package/dist/manager/libraries/ai/providers/openai.js +1272 -0
  183. package/dist/manager/libraries/ai/providers/test.js +257 -0
  184. package/dist/manager/libraries/ai/tokens.js +54 -0
  185. package/dist/manager/libraries/analytics/conversions.js +478 -0
  186. package/dist/manager/libraries/analytics/match-data.js +573 -0
  187. package/dist/manager/libraries/analytics/signup.js +120 -0
  188. package/dist/manager/libraries/auth-user.js +46 -0
  189. package/dist/manager/libraries/content/feed-parser.js +198 -0
  190. package/dist/manager/libraries/content/ghostii.js +191 -0
  191. package/dist/manager/libraries/content/source-resolver.js +629 -0
  192. package/dist/manager/libraries/email/constants.js +532 -0
  193. package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
  194. package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
  195. package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
  196. package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
  197. package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
  198. package/dist/manager/libraries/email/data/typo-domains.js +83 -0
  199. package/dist/manager/libraries/email/disposable-domains.js +92 -0
  200. package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
  201. package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
  202. package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
  203. package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
  204. package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
  205. package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
  206. package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
  207. package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
  208. package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
  209. package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
  210. package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
  211. package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
  212. package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
  213. package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
  214. package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
  215. package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
  216. package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
  217. package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
  218. package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
  219. package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
  220. package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
  221. package/dist/manager/libraries/email/generators/newsletter.js +899 -0
  222. package/dist/manager/libraries/email/index.js +146 -0
  223. package/dist/manager/libraries/email/marketing/index.js +754 -0
  224. package/dist/manager/libraries/email/prepare.js +365 -0
  225. package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
  226. package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
  227. package/dist/manager/libraries/email/transactional/index.js +510 -0
  228. package/dist/manager/libraries/email/utm.js +86 -0
  229. package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
  230. package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
  231. package/dist/manager/libraries/email/validation.js +255 -0
  232. package/dist/manager/libraries/env.js +198 -0
  233. package/dist/manager/libraries/infer-contact.js +101 -0
  234. package/dist/manager/libraries/load-provider.js +65 -0
  235. package/dist/manager/libraries/notification.js +256 -0
  236. package/dist/manager/libraries/openai.js +13 -0
  237. package/dist/manager/libraries/payment/discount-codes.js +204 -0
  238. package/dist/manager/libraries/payment/fetch-failure.js +56 -0
  239. package/dist/manager/libraries/payment/license.js +44 -0
  240. package/dist/manager/libraries/payment/order-id.js +57 -0
  241. package/dist/manager/libraries/payment/provider-errors.js +71 -0
  242. package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
  243. package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
  244. package/dist/manager/libraries/payment/providers/paypal.js +987 -0
  245. package/dist/manager/libraries/payment/providers/stripe.js +755 -0
  246. package/dist/manager/libraries/payment/providers/test.js +250 -0
  247. package/dist/manager/libraries/payment/refund-linkage.js +78 -0
  248. package/dist/manager/libraries/payment/refund-policy.js +113 -0
  249. package/dist/manager/libraries/payment/winback.js +55 -0
  250. package/dist/manager/libraries/prompts/infer-contact.md +78 -0
  251. package/dist/manager/libraries/rate-limits.js +17 -0
  252. package/dist/manager/libraries/recaptcha.js +58 -0
  253. package/dist/manager/libraries/user-doc.js +234 -0
  254. package/dist/manager/routes/admin/backup/post.js +106 -0
  255. package/dist/manager/routes/admin/cron/post.js +37 -0
  256. package/dist/manager/routes/admin/database/get.js +35 -0
  257. package/dist/manager/routes/admin/database/post.js +34 -0
  258. package/dist/manager/routes/admin/email/post.js +74 -0
  259. package/dist/manager/routes/admin/firestore/get.js +35 -0
  260. package/dist/manager/routes/admin/firestore/post.js +56 -0
  261. package/dist/manager/routes/admin/firestore/query/post.js +128 -0
  262. package/dist/manager/routes/admin/hook/post.js +104 -0
  263. package/dist/manager/routes/admin/infer-contact/post.js +35 -0
  264. package/dist/manager/routes/admin/notification/post.js +37 -0
  265. package/dist/manager/routes/admin/payment/post.js +56 -0
  266. package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
  267. package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
  268. package/dist/manager/routes/admin/post/post.js +487 -0
  269. package/dist/manager/routes/admin/post/put.js +146 -0
  270. package/dist/manager/routes/admin/post/templates/post.html +14 -0
  271. package/dist/manager/routes/admin/repo/content/post.js +106 -0
  272. package/dist/manager/routes/admin/stats/get.js +222 -0
  273. package/dist/manager/routes/admin/users/disable/post.js +50 -0
  274. package/dist/manager/routes/admin/users/list/get.js +110 -0
  275. package/dist/manager/routes/admin/users/sync/post.js +119 -0
  276. package/dist/manager/routes/brand/get.js +26 -0
  277. package/dist/manager/routes/content/post/get.js +110 -0
  278. package/dist/manager/routes/general/email/post.js +89 -0
  279. package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
  280. package/dist/manager/routes/general/uuid/post.js +31 -0
  281. package/dist/manager/routes/handler/post/post.js +142 -0
  282. package/dist/manager/routes/health/get.js +43 -0
  283. package/dist/manager/routes/index.js +11 -0
  284. package/dist/manager/routes/marketing/campaign/delete.js +45 -0
  285. package/dist/manager/routes/marketing/campaign/get.js +69 -0
  286. package/dist/manager/routes/marketing/campaign/post.js +124 -0
  287. package/dist/manager/routes/marketing/campaign/put.js +86 -0
  288. package/dist/manager/routes/marketing/campaign/utils.js +59 -0
  289. package/dist/manager/routes/marketing/contact/delete.js +100 -0
  290. package/dist/manager/routes/marketing/contact/post.js +149 -0
  291. package/dist/manager/routes/marketing/contact/put.js +37 -0
  292. package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
  293. package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
  294. package/dist/manager/routes/marketing/webhook/post.js +123 -0
  295. package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
  296. package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
  297. package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
  298. package/dist/manager/routes/payments/cancel/post.js +149 -0
  299. package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
  300. package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
  301. package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
  302. package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
  303. package/dist/manager/routes/payments/discount/get.js +25 -0
  304. package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
  305. package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
  306. package/dist/manager/routes/payments/intent/post.js +341 -0
  307. package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
  308. package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
  309. package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
  310. package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
  311. package/dist/manager/routes/payments/intent/providers/test.js +341 -0
  312. package/dist/manager/routes/payments/plan/post.js +147 -0
  313. package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
  314. package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
  315. package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
  316. package/dist/manager/routes/payments/plan/providers/test.js +113 -0
  317. package/dist/manager/routes/payments/portal/post.js +113 -0
  318. package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
  319. package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
  320. package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
  321. package/dist/manager/routes/payments/portal/providers/test.js +18 -0
  322. package/dist/manager/routes/payments/refund/post.js +207 -0
  323. package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
  324. package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
  325. package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
  326. package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
  327. package/dist/manager/routes/payments/refund/providers/test.js +204 -0
  328. package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
  329. package/dist/manager/routes/payments/uncancel/post.js +106 -0
  330. package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
  331. package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
  332. package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
  333. package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
  334. package/dist/manager/routes/payments/webhook/post.js +157 -0
  335. package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
  336. package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
  337. package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
  338. package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
  339. package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
  340. package/dist/manager/routes/payments/winback/post.js +276 -0
  341. package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
  342. package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
  343. package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
  344. package/dist/manager/routes/payments/winback/providers/test.js +42 -0
  345. package/dist/manager/routes/restart/index.js +37 -0
  346. package/dist/manager/routes/special/electron-client/post.js +59 -0
  347. package/dist/manager/routes/test/authenticate/get.js +11 -0
  348. package/dist/manager/routes/test/health/get.js +8 -0
  349. package/dist/manager/routes/test/index.js +16 -0
  350. package/dist/manager/routes/test/lab/post.js +17 -0
  351. package/dist/manager/routes/test/redirect/get.js +27 -0
  352. package/dist/manager/routes/test/reset-account/post.js +79 -0
  353. package/dist/manager/routes/test/roster/get.js +40 -0
  354. package/dist/manager/routes/test/schema/post.js +20 -0
  355. package/dist/manager/routes/test/usage/post.js +44 -0
  356. package/dist/manager/routes/test/webhook/post.js +21 -0
  357. package/dist/manager/routes/user/api-keys/post.js +51 -0
  358. package/dist/manager/routes/user/connections/_context.js +194 -0
  359. package/dist/manager/routes/user/connections/_grant.js +247 -0
  360. package/dist/manager/routes/user/connections/_lease.js +184 -0
  361. package/dist/manager/routes/user/connections/_providers.js +191 -0
  362. package/dist/manager/routes/user/connections/_state.js +117 -0
  363. package/dist/manager/routes/user/connections/delete.js +55 -0
  364. package/dist/manager/routes/user/connections/get.js +167 -0
  365. package/dist/manager/routes/user/connections/post.js +370 -0
  366. package/dist/manager/routes/user/connections/providers/discord.js +41 -0
  367. package/dist/manager/routes/user/connections/providers/google.js +55 -0
  368. package/dist/manager/routes/user/connections/providers/kick.js +68 -0
  369. package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
  370. package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
  371. package/dist/manager/routes/user/data-request/delete.js +84 -0
  372. package/dist/manager/routes/user/data-request/get.js +229 -0
  373. package/dist/manager/routes/user/data-request/post.js +136 -0
  374. package/dist/manager/routes/user/delete.js +149 -0
  375. package/dist/manager/routes/user/feedback/post.js +81 -0
  376. package/dist/manager/routes/user/get.js +14 -0
  377. package/dist/manager/routes/user/orders/get.js +95 -0
  378. package/dist/manager/routes/user/sessions/delete.js +95 -0
  379. package/dist/manager/routes/user/sessions/get.js +40 -0
  380. package/dist/manager/routes/user/settings/validate/post.js +86 -0
  381. package/dist/manager/routes/user/signup/post.js +662 -0
  382. package/dist/manager/routes/user/subscription/get.js +77 -0
  383. package/dist/manager/routes/user/token/post.js +30 -0
  384. package/dist/manager/routes/verts/delete.js +43 -0
  385. package/dist/manager/routes/verts/get.js +49 -0
  386. package/dist/manager/routes/verts/post.js +61 -0
  387. package/dist/manager/routes/verts/put.js +75 -0
  388. package/dist/manager/routes/verts/redirect/get.js +36 -0
  389. package/dist/manager/routes/verts/serve/get.js +67 -0
  390. package/dist/manager/routes/verts/utils.js +436 -0
  391. package/dist/manager/schemas/admin/backup/post.js +8 -0
  392. package/dist/manager/schemas/admin/cron/post.js +8 -0
  393. package/dist/manager/schemas/admin/database/get.js +5 -0
  394. package/dist/manager/schemas/admin/database/post.js +6 -0
  395. package/dist/manager/schemas/admin/email/post.js +31 -0
  396. package/dist/manager/schemas/admin/firestore/get.js +5 -0
  397. package/dist/manager/schemas/admin/firestore/post.js +8 -0
  398. package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
  399. package/dist/manager/schemas/admin/hook/post.js +8 -0
  400. package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
  401. package/dist/manager/schemas/admin/notification/post.js +23 -0
  402. package/dist/manager/schemas/admin/payment/post.js +8 -0
  403. package/dist/manager/schemas/admin/post/post.js +23 -0
  404. package/dist/manager/schemas/admin/post/put.js +13 -0
  405. package/dist/manager/schemas/admin/repo/content/post.js +10 -0
  406. package/dist/manager/schemas/admin/stats/get.js +5 -0
  407. package/dist/manager/schemas/admin/users/disable/post.js +9 -0
  408. package/dist/manager/schemas/admin/users/list/get.js +10 -0
  409. package/dist/manager/schemas/admin/users/sync/post.js +7 -0
  410. package/dist/manager/schemas/brand/get.js +3 -0
  411. package/dist/manager/schemas/content/post/get.js +8 -0
  412. package/dist/manager/schemas/general/email/post.js +10 -0
  413. package/dist/manager/schemas/general/uuid/post.js +9 -0
  414. package/dist/manager/schemas/handler/post/post.js +13 -0
  415. package/dist/manager/schemas/health/get.js +3 -0
  416. package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
  417. package/dist/manager/schemas/marketing/campaign/get.js +13 -0
  418. package/dist/manager/schemas/marketing/campaign/post.js +43 -0
  419. package/dist/manager/schemas/marketing/campaign/put.js +37 -0
  420. package/dist/manager/schemas/marketing/contact/delete.js +8 -0
  421. package/dist/manager/schemas/marketing/contact/post.js +14 -0
  422. package/dist/manager/schemas/marketing/contact/put.js +8 -0
  423. package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
  424. package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
  425. package/dist/manager/schemas/marketing/webhook/post.js +7 -0
  426. package/dist/manager/schemas/payments/cancel/post.js +15 -0
  427. package/dist/manager/schemas/payments/discount/get.js +8 -0
  428. package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
  429. package/dist/manager/schemas/payments/intent/post.js +28 -0
  430. package/dist/manager/schemas/payments/plan/post.js +11 -0
  431. package/dist/manager/schemas/payments/portal/post.js +12 -0
  432. package/dist/manager/schemas/payments/refund/post.js +14 -0
  433. package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
  434. package/dist/manager/schemas/payments/uncancel/post.js +9 -0
  435. package/dist/manager/schemas/payments/webhook/post.js +6 -0
  436. package/dist/manager/schemas/payments/winback/post.js +12 -0
  437. package/dist/manager/schemas/restart/index.js +5 -0
  438. package/dist/manager/schemas/special/electron-client/post.js +11 -0
  439. package/dist/manager/schemas/test/authenticate/get.js +3 -0
  440. package/dist/manager/schemas/test/health/get.js +3 -0
  441. package/dist/manager/schemas/test/index.js +5 -0
  442. package/dist/manager/schemas/test/lab/post.js +3 -0
  443. package/dist/manager/schemas/test/redirect/get.js +7 -0
  444. package/dist/manager/schemas/test/reset-account/post.js +3 -0
  445. package/dist/manager/schemas/test/roster/get.js +3 -0
  446. package/dist/manager/schemas/test/schema/post.js +70 -0
  447. package/dist/manager/schemas/test/usage/post.js +8 -0
  448. package/dist/manager/schemas/test/webhook/post.js +7 -0
  449. package/dist/manager/schemas/user/api-keys/post.js +6 -0
  450. package/dist/manager/schemas/user/connections/delete.js +8 -0
  451. package/dist/manager/schemas/user/connections/get.js +21 -0
  452. package/dist/manager/schemas/user/connections/post.js +12 -0
  453. package/dist/manager/schemas/user/data-request/delete.js +3 -0
  454. package/dist/manager/schemas/user/data-request/get.js +5 -0
  455. package/dist/manager/schemas/user/data-request/post.js +6 -0
  456. package/dist/manager/schemas/user/delete.js +6 -0
  457. package/dist/manager/schemas/user/feedback/post.js +8 -0
  458. package/dist/manager/schemas/user/get.js +3 -0
  459. package/dist/manager/schemas/user/orders/get.js +6 -0
  460. package/dist/manager/schemas/user/sessions/delete.js +6 -0
  461. package/dist/manager/schemas/user/sessions/get.js +6 -0
  462. package/dist/manager/schemas/user/settings/validate/post.js +8 -0
  463. package/dist/manager/schemas/user/signup/post.js +27 -0
  464. package/dist/manager/schemas/user/subscription/get.js +5 -0
  465. package/dist/manager/schemas/user/token/post.js +5 -0
  466. package/dist/manager/schemas/verts/delete.js +8 -0
  467. package/dist/manager/schemas/verts/get.js +9 -0
  468. package/dist/manager/schemas/verts/post.js +23 -0
  469. package/dist/manager/schemas/verts/put.js +20 -0
  470. package/dist/manager/schemas/verts/redirect/get.js +10 -0
  471. package/dist/manager/schemas/verts/serve/get.js +14 -0
  472. package/dist/manager/server-manager.js +69 -0
  473. package/dist/mcp/client.js +98 -0
  474. package/dist/mcp/handler.js +521 -0
  475. package/dist/mcp/index.js +157 -0
  476. package/dist/mcp/tools.js +555 -0
  477. package/dist/mcp/utils.js +108 -0
  478. package/dist/omega-bin.js +6 -0
  479. package/dist/require.js +3 -0
  480. package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
  481. package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
  482. package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
  483. package/dist/test/fixtures/firebase-project/firebase.json +49 -0
  484. package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
  485. package/dist/test/fixtures/firebase-project/package.json +14 -0
  486. package/dist/test/fixtures/firebase-project/src/index.js +11 -0
  487. package/dist/test/fixtures/firebase-project/storage.rules +8 -0
  488. package/dist/test/parse-audit.js +25 -0
  489. package/dist/test/run-tests.js +90 -0
  490. package/dist/test/runner.js +982 -0
  491. package/dist/test/seed.js +242 -0
  492. package/dist/test/test-accounts.js +2660 -0
  493. package/dist/test/utils/assertions.js +189 -0
  494. package/dist/test/utils/email-capture.js +252 -0
  495. package/dist/test/utils/extended-mode-warning.js +11 -0
  496. package/dist/test/utils/firestore-rules-client.js +277 -0
  497. package/dist/test/utils/http-client.js +228 -0
  498. package/dist/test/utils/test-mode-file.js +192 -0
  499. package/dist/utils/merge-line-files.js +6 -0
  500. package/dist/utils/scaffold-defaults.js +71 -0
  501. package/dist/utils/test-lanes.js +62 -0
  502. package/dist/vendor/account/engine.js +182 -0
  503. package/dist/vendor/account/features.js +220 -0
  504. package/dist/vendor/account/index.js +53 -0
  505. package/dist/vendor/account/schema.js +272 -0
  506. package/dist/vendor/account/subscription.js +38 -0
  507. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  508. package/dist/vendor/analytics/adapters/meta.js +26 -0
  509. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  510. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  511. package/dist/vendor/analytics/catalog.js +908 -0
  512. package/dist/vendor/analytics/identity.js +136 -0
  513. package/dist/vendor/config/company.js +31 -0
  514. package/dist/vendor/config/defaults.js +173 -0
  515. package/dist/vendor/config/demo.js +18 -0
  516. package/dist/vendor/config/desktop-artifacts.js +110 -0
  517. package/dist/vendor/config/edit.js +769 -0
  518. package/dist/vendor/config/env-delivery.js +145 -0
  519. package/dist/vendor/config/env-rules.js +93 -0
  520. package/dist/vendor/config/env-schema.js +1078 -0
  521. package/dist/vendor/config/env.js +445 -0
  522. package/dist/vendor/config/hooks.js +97 -0
  523. package/dist/vendor/config/index.js +237 -0
  524. package/dist/vendor/config/instances.js +208 -0
  525. package/dist/vendor/config/load.js +490 -0
  526. package/dist/vendor/config/merge.js +68 -0
  527. package/dist/vendor/config/order.js +139 -0
  528. package/dist/vendor/config/ports.js +374 -0
  529. package/dist/vendor/config/providers.js +32 -0
  530. package/dist/vendor/config/repo.js +142 -0
  531. package/dist/vendor/config/retired-keys.js +430 -0
  532. package/dist/vendor/config/schema.js +1610 -0
  533. package/dist/vendor/config/secrets.js +50 -0
  534. package/dist/vendor/config/seed.js +34 -0
  535. package/dist/vendor/config/site-global.js +205 -0
  536. package/dist/vendor/config/validate.js +554 -0
  537. package/dist/vendor/config/winback.js +61 -0
  538. package/dist/vendor/devkit/attach-log-file.js +262 -0
  539. package/dist/vendor/devkit/bare-requires.js +153 -0
  540. package/dist/vendor/devkit/command-path.js +46 -0
  541. package/dist/vendor/devkit/defaults-engine.js +372 -0
  542. package/dist/vendor/devkit/deploy-record.js +180 -0
  543. package/dist/vendor/devkit/deploy.js +286 -0
  544. package/dist/vendor/devkit/license.js +155 -0
  545. package/dist/vendor/devkit/local-https.js +360 -0
  546. package/dist/vendor/devkit/local.js +1905 -0
  547. package/dist/vendor/devkit/logger.js +128 -0
  548. package/dist/vendor/devkit/merge-line-files.js +296 -0
  549. package/dist/vendor/devkit/npm-registry.js +52 -0
  550. package/dist/vendor/devkit/omega-bin.js +345 -0
  551. package/dist/vendor/devkit/parse-audit.js +74 -0
  552. package/dist/vendor/devkit/safe-install.js +18 -0
  553. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  554. package/dist/vendor/devkit/stop-signals.js +28 -0
  555. package/dist/vendor/devkit/test/assert.js +120 -0
  556. package/dist/vendor/devkit/test/define-cases.js +104 -0
  557. package/dist/vendor/devkit/test/runner-core.js +554 -0
  558. package/dist/vendor/devkit/test/scope.js +162 -0
  559. package/dist/vendor/devkit/update.js +569 -0
  560. package/dist/vendor/monitoring/core.js +180 -0
  561. package/dist/vendor/monitoring/env.js +49 -0
  562. package/dist/vendor/monitoring/logger.js +39 -0
  563. package/dist/vendor/monitoring/node.js +72 -0
  564. package/docs/admin-post-route.md +57 -0
  565. package/docs/ai-library.md +171 -0
  566. package/docs/architecture.md +62 -0
  567. package/docs/audit.md +70 -0
  568. package/docs/auth-hooks.md +74 -0
  569. package/docs/build-system.md +65 -0
  570. package/docs/cdp-debugging.md +29 -0
  571. package/docs/cli-firestore-auth.md +85 -0
  572. package/docs/cli-logs.md +67 -0
  573. package/docs/cli-output.md +146 -0
  574. package/docs/code-patterns.md +77 -0
  575. package/docs/common-mistakes.md +12 -0
  576. package/docs/common-operations.md +60 -0
  577. package/docs/connections.md +212 -0
  578. package/docs/consent.md +362 -0
  579. package/docs/directory-structure.md +150 -0
  580. package/docs/email-system.md +459 -0
  581. package/docs/environment-detection.md +93 -0
  582. package/docs/file-naming.md +10 -0
  583. package/docs/firestore.md +134 -0
  584. package/docs/ghostii.md +240 -0
  585. package/docs/index.md +299 -0
  586. package/docs/key-files.md +37 -0
  587. package/docs/logging.md +60 -0
  588. package/docs/marketing-campaigns.md +407 -0
  589. package/docs/marketing-fields.md +25 -0
  590. package/docs/mcp.md +222 -0
  591. package/docs/migration.md +129 -0
  592. package/docs/payment-system.md +856 -0
  593. package/docs/paypal-sandbox-qa.md +105 -0
  594. package/docs/response-headers.md +9 -0
  595. package/docs/routes.md +211 -0
  596. package/docs/sanitization.md +71 -0
  597. package/docs/schemas.md +163 -0
  598. package/docs/shared/agent-docs.md +89 -0
  599. package/docs/shared/analytics.md +612 -0
  600. package/docs/shared/brands.md +51 -0
  601. package/docs/shared/breaking-changes.md +497 -0
  602. package/docs/shared/config.md +1387 -0
  603. package/docs/shared/deploys.md +215 -0
  604. package/docs/shared/icons.md +201 -0
  605. package/docs/shared/local-dev.md +147 -0
  606. package/docs/shared/logging.md +202 -0
  607. package/docs/shared/monitoring.md +153 -0
  608. package/docs/shared/publishing.md +183 -0
  609. package/docs/shared/rulings.md +34 -0
  610. package/docs/shared/testing.md +147 -0
  611. package/docs/shared/theming.md +604 -0
  612. package/docs/shared/translation.md +291 -0
  613. package/docs/shared/updates.md +61 -0
  614. package/docs/stripe-webhook-forwarding.md +20 -0
  615. package/docs/test-boot-layer.md +67 -0
  616. package/docs/test-framework.md +583 -0
  617. package/docs/usage-rate-limiting.md +121 -0
  618. package/docs/verts.md +29 -0
  619. package/package.json +143 -0
  620. package/templates/config/omega.json5 +322 -0
  621. package/templates/database.rules.json +82 -0
  622. package/templates/firebase.json +67 -0
  623. package/templates/firestore.framework.rules +191 -0
  624. package/templates/firestore.indexes.json +4 -0
  625. package/templates/firestore.rules +59 -0
  626. package/templates/index.js +12 -0
  627. package/templates/public/404.html +26 -0
  628. package/templates/public/index.html +24 -0
  629. package/templates/remoteconfig.template.json +1 -0
  630. package/templates/storage-lifecycle-config-1-day.json +9 -0
  631. package/templates/storage-lifecycle-config-30-days.json +9 -0
  632. package/templates/storage.rules +11 -0
  633. package/test/_init/accounts-validation.js +58 -0
  634. package/test/ai/tools-live.test.js +171 -0
  635. package/test/analytics/conversion-delivery.test.js +1013 -0
  636. package/test/analytics/match-normalization.test.js +238 -0
  637. package/test/analytics/signup-conversion.test.js +135 -0
  638. package/test/boot/cli-dispatch.test.js +142 -0
  639. package/test/boot/defaults-scaffold.test.js +248 -0
  640. package/test/boot/deploy-staging.test.js +446 -0
  641. package/test/boot/disposable-domains.test.js +154 -0
  642. package/test/boot/emulator-boots.test.js +39 -0
  643. package/test/boot/emulator-port-preflight.test.js +126 -0
  644. package/test/boot/emulator-ready-timeout.test.js +46 -0
  645. package/test/boot/project-id-refresh.test.js +73 -0
  646. package/test/boot/suite-portability.test.js +278 -0
  647. package/test/boot/update-command.test.js +63 -0
  648. package/test/cli/cross-platform.test.js +168 -0
  649. package/test/cli/custom-project-type.test.js +426 -0
  650. package/test/cli/deploy-license-env.test.js +100 -0
  651. package/test/cli/emulator-adoption.test.js +254 -0
  652. package/test/cli/emulator-orphans.test.js +658 -0
  653. package/test/cli/emulator-ownership.test.js +177 -0
  654. package/test/cli/emulator-port-retry.test.js +395 -0
  655. package/test/cli/emulator-shutdown.test.js +788 -0
  656. package/test/cli/emulator-stale-reap.test.js +574 -0
  657. package/test/cli/ensure-target.test.js +325 -0
  658. package/test/cli/flags.test.js +64 -0
  659. package/test/cli/https-trust.test.js +38 -0
  660. package/test/cli/install-update-dispatch.test.js +47 -0
  661. package/test/cli/lane-environments.test.js +159 -0
  662. package/test/cli/marketing-campaigns-seeded.test.js +194 -0
  663. package/test/cli/migrate-bare-requires.test.js +128 -0
  664. package/test/cli/migrate-markers.test.js +530 -0
  665. package/test/cli/required-indexes.test.js +64 -0
  666. package/test/cli/rules-compile.test.js +622 -0
  667. package/test/cli/rules-migration-deferral.test.js +259 -0
  668. package/test/cli/rules-version.test.js +104 -0
  669. package/test/cli/setup-load-files.test.js +66 -0
  670. package/test/cli/setup-offline-mode.test.js +276 -0
  671. package/test/cli/setup-retired.test.js +64 -0
  672. package/test/cli/setup-shared-project-indexes.test.js +262 -0
  673. package/test/cli/setup-tests-requires.test.js +45 -0
  674. package/test/cli/stage-env-compose.test.js +316 -0
  675. package/test/cli/stripe-live-lane.test.js +303 -0
  676. package/test/cli/target.test.js +182 -0
  677. package/test/cli/templates.test.js +66 -0
  678. package/test/cli/test-runner-env.test.js +115 -0
  679. package/test/cli/test-stack-shutdown.test.js +275 -0
  680. package/test/cli/test-target-no-match.test.js +128 -0
  681. package/test/cli/verb-logs.test.js +118 -0
  682. package/test/cli/version-dispatch.test.js +39 -0
  683. package/test/content/blog-generate.test.js +164 -0
  684. package/test/email/campaign-config-fault.test.js +376 -0
  685. package/test/email/campaign-cron-pipeline.test.js +536 -0
  686. package/test/email/campaign-send.test.js +47 -0
  687. package/test/email/consent-lifecycle.test.js +258 -0
  688. package/test/email/content-html-policy.test.js +270 -0
  689. package/test/email/feedback-and-plain-send.test.js +54 -0
  690. package/test/email/fixtures/clean.json +30 -0
  691. package/test/email/fixtures/editorial.json +30 -0
  692. package/test/email/fixtures/field-report.json +53 -0
  693. package/test/email/identity.test.js +321 -0
  694. package/test/email/marketing/consent-gate.test.js +265 -0
  695. package/test/email/marketing/custom-fields-catalog.test.js +259 -0
  696. package/test/email/marketing/prune-per-provider.test.js +762 -0
  697. package/test/email/marketing/remove-log-privacy.test.js +225 -0
  698. package/test/email/marketing-lifecycle.test.js +139 -0
  699. package/test/email/newsletter-generate.test.js +853 -0
  700. package/test/email/newsletter-svg-tokens.test.js +51 -0
  701. package/test/email/newsletter-templates.test.js +492 -0
  702. package/test/email/order-one-time-cta.test.js +97 -0
  703. package/test/email/render-content.test.js +248 -0
  704. package/test/email/safe-url.test.js +0 -0
  705. package/test/email/sanitize-images.test.js +66 -0
  706. package/test/email/send-log-privacy.test.js +107 -0
  707. package/test/email/templates.test.js +288 -0
  708. package/test/email/testing-capture.test.js +260 -0
  709. package/test/email/transactional-send.test.js +36 -0
  710. package/test/email/transactional.test.js +562 -0
  711. package/test/email/unsubscribe-groups.test.js +155 -0
  712. package/test/email/unsubscribe-key.test.js +91 -0
  713. package/test/email/validation-cases.test.js +152 -0
  714. package/test/email/validation.test.js +746 -0
  715. package/test/events/auth-delete-conversion.test.js +138 -0
  716. package/test/events/auth-delete-race.test.js +211 -0
  717. package/test/events/auth-on-create-log.test.js +164 -0
  718. package/test/events/auth-signup-conversion.test.js +141 -0
  719. package/test/events/auth-signup-limit.test.js +142 -0
  720. package/test/events/auth-trigger-log-privacy.test.js +256 -0
  721. package/test/events/cron-job-doc-shape.test.js +101 -0
  722. package/test/events/cron-reset-usage.test.js +116 -0
  723. package/test/events/notification-conversion.test.js +182 -0
  724. package/test/events/payments/_webhook-harness.js +307 -0
  725. package/test/events/payments/abandoned-cart-activity.test.js +135 -0
  726. package/test/events/payments/analytics-payment-events.test.js +945 -0
  727. package/test/events/payments/dispute-email-status.test.js +55 -0
  728. package/test/events/payments/journey-payments-abandoned.test.js +126 -0
  729. package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
  730. package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
  731. package/test/events/payments/journey-payments-cancel.test.js +184 -0
  732. package/test/events/payments/journey-payments-decline.test.js +225 -0
  733. package/test/events/payments/journey-payments-discount.test.js +91 -0
  734. package/test/events/payments/journey-payments-dispute.test.js +190 -0
  735. package/test/events/payments/journey-payments-failure.test.js +148 -0
  736. package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
  737. package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
  738. package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
  739. package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
  740. package/test/events/payments/journey-payments-one-time.test.js +175 -0
  741. package/test/events/payments/journey-payments-plan-change.test.js +146 -0
  742. package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
  743. package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
  744. package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
  745. package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
  746. package/test/events/payments/journey-payments-suspend.test.js +183 -0
  747. package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
  748. package/test/events/payments/journey-payments-trial.test.js +173 -0
  749. package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
  750. package/test/events/payments/journey-payments-uncancel.test.js +157 -0
  751. package/test/events/payments/journey-payments-upgrade.test.js +137 -0
  752. package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
  753. package/test/events/payments/journey-payments-winback.test.js +195 -0
  754. package/test/events/payments/paypal-expiry-cron.test.js +166 -0
  755. package/test/events/payments/purchase-failed-handler.test.js +80 -0
  756. package/test/events/payments/purchase-refunded-handler.test.js +108 -0
  757. package/test/events/payments/test-processor-doc-shape.test.js +162 -0
  758. package/test/events/payments/transition-order-emails.test.js +185 -0
  759. package/test/events/payments/transition-promo-lines.test.js +192 -0
  760. package/test/events/payments/transitions-detect.test.js +767 -0
  761. package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
  762. package/test/events/payments/trial-lapse-sweep.test.js +359 -0
  763. package/test/events/payments/webhook-atomic-writes.test.js +69 -0
  764. package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
  765. package/test/events/payments/webhook-discount-clear.test.js +189 -0
  766. package/test/events/payments/webhook-failure-intent.test.js +93 -0
  767. package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
  768. package/test/events/payments/webhook-ordering.test.js +326 -0
  769. package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
  770. package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
  771. package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
  772. package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
  773. package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
  774. package/test/events/payments/webhook-refund-without-order.test.js +170 -0
  775. package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
  776. package/test/events/payments/webhook-refused-intent.test.js +142 -0
  777. package/test/events/payments/webhook-retry-sweep.test.js +252 -0
  778. package/test/events/payments/webhook-transition-claim.test.js +389 -0
  779. package/test/events/payments/webhook-uid-trust.test.js +203 -0
  780. package/test/events/payments/webhook-user-without-auth.test.js +161 -0
  781. package/test/fixtures/chargebee/invoice-one-time.json +27 -0
  782. package/test/fixtures/chargebee/subscription-active.json +44 -0
  783. package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
  784. package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
  785. package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
  786. package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
  787. package/test/fixtures/chargebee/subscription-paused.json +42 -0
  788. package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
  789. package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
  790. package/test/fixtures/coinbase/charge-confirmed.json +50 -0
  791. package/test/fixtures/coinbase/charge-failed.json +34 -0
  792. package/test/fixtures/coinbase/charge-pending.json +34 -0
  793. package/test/fixtures/migrate/ported-route.js.txt +11 -0
  794. package/test/fixtures/paypal/capture-completed.json +51 -0
  795. package/test/fixtures/paypal/capture-refunded.json +43 -0
  796. package/test/fixtures/paypal/order-approved.json +62 -0
  797. package/test/fixtures/paypal/order-completed.json +110 -0
  798. package/test/fixtures/paypal/sale-refunded.json +38 -0
  799. package/test/fixtures/paypal/subscription-active.json +76 -0
  800. package/test/fixtures/paypal/subscription-cancelled.json +50 -0
  801. package/test/fixtures/paypal/subscription-suspended.json +65 -0
  802. package/test/fixtures/stripe/checkout-session-completed.json +130 -0
  803. package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
  804. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
  805. package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
  806. package/test/fixtures/stripe/subscription-active.json +161 -0
  807. package/test/fixtures/stripe/subscription-canceled.json +161 -0
  808. package/test/fixtures/stripe/subscription-trialing.json +161 -0
  809. package/test/helpers/_shared-config.js +15 -0
  810. package/test/helpers/ai-request-payload.test.js +619 -0
  811. package/test/helpers/ai-schema-resolve.test.js +125 -0
  812. package/test/helpers/ai-test-provider.test.js +227 -0
  813. package/test/helpers/ai-token-accounting.test.js +150 -0
  814. package/test/helpers/ai-tools-format.test.js +384 -0
  815. package/test/helpers/analytics-no-id-notice.test.js +146 -0
  816. package/test/helpers/analytics-user-data.test.js +194 -0
  817. package/test/helpers/api-manager.test.js +311 -0
  818. package/test/helpers/backend-router.test.js +136 -0
  819. package/test/helpers/content/blog-auto-publisher.test.js +482 -0
  820. package/test/helpers/content/feed-parser.test.js +529 -0
  821. package/test/helpers/content/ghostii-blocks.test.js +135 -0
  822. package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
  823. package/test/helpers/content/ghostii-write-article.test.js +244 -0
  824. package/test/helpers/dev-only-routes.test.js +225 -0
  825. package/test/helpers/env-reader.test.js +362 -0
  826. package/test/helpers/environment.test.js +263 -0
  827. package/test/helpers/event-middleware.test.js +407 -0
  828. package/test/helpers/infer-contact.test.js +157 -0
  829. package/test/helpers/lane-url.test.js +90 -0
  830. package/test/helpers/merge-line-files.test.js +272 -0
  831. package/test/helpers/metadata.test.js +122 -0
  832. package/test/helpers/middleware-request-log.test.js +134 -0
  833. package/test/helpers/middleware-user-log.test.js +124 -0
  834. package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
  835. package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
  836. package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
  837. package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
  838. package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
  839. package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
  840. package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
  841. package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
  842. package/test/helpers/payment/discount-codes.test.js +141 -0
  843. package/test/helpers/payment/extract-resource.test.js +113 -0
  844. package/test/helpers/payment/fetch-failure.test.js +215 -0
  845. package/test/helpers/payment/license-gate.test.js +108 -0
  846. package/test/helpers/payment/order-id.test.js +99 -0
  847. package/test/helpers/payment/paypal/create-intent.test.js +382 -0
  848. package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
  849. package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
  850. package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
  851. package/test/helpers/payment/paypal/refund-details.test.js +202 -0
  852. package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
  853. package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
  854. package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
  855. package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
  856. package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
  857. package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
  858. package/test/helpers/payment/stripe/refund-details.test.js +147 -0
  859. package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
  860. package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
  861. package/test/helpers/persona-domain.test.js +87 -0
  862. package/test/helpers/recaptcha.test.js +166 -0
  863. package/test/helpers/resolved-config.test.js +106 -0
  864. package/test/helpers/response-log-redaction.test.js +262 -0
  865. package/test/helpers/roles.test.js +155 -0
  866. package/test/helpers/route-context-debug-gate.test.js +118 -0
  867. package/test/helpers/route-context-logging.test.js +214 -0
  868. package/test/helpers/route-context.test.js +129 -0
  869. package/test/helpers/safe-compare.test.js +114 -0
  870. package/test/helpers/sanitize.test.js +223 -0
  871. package/test/helpers/schema-engine.test.js +537 -0
  872. package/test/helpers/schema-zod.test.js +554 -0
  873. package/test/helpers/seed-accounts-load.test.js +163 -0
  874. package/test/helpers/seed-google-personas.test.js +114 -0
  875. package/test/helpers/seeded-personas.test.js +823 -0
  876. package/test/helpers/settings.test.js +292 -0
  877. package/test/helpers/setup-engines-pin.test.js +68 -0
  878. package/test/helpers/setup-manifest-sync.test.js +116 -0
  879. package/test/helpers/slugify.test.js +395 -0
  880. package/test/helpers/storage.test.js +195 -0
  881. package/test/helpers/test-banner-latch.test.js +139 -0
  882. package/test/helpers/usage-consume.test.js +460 -0
  883. package/test/helpers/usage-log-privacy.test.js +121 -0
  884. package/test/helpers/user-doc-heal.test.js +540 -0
  885. package/test/helpers/user.test.js +771 -0
  886. package/test/helpers/webhook-forward.test.js +419 -0
  887. package/test/helpers/wipe-auth-project.test.js +143 -0
  888. package/test/mcp/discovery.test.js +52 -0
  889. package/test/mcp/oauth.test.js +160 -0
  890. package/test/mcp/protocol.test.js +278 -0
  891. package/test/mcp/roles.test.js +203 -0
  892. package/test/mcp/utils.test.js +245 -0
  893. package/test/notification/identity.test.js +155 -0
  894. package/test/routes/admin/create-post.test.js +362 -0
  895. package/test/routes/admin/database.test.js +133 -0
  896. package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
  897. package/test/routes/admin/email-content-html.test.js +58 -0
  898. package/test/routes/admin/email-request-log.test.js +86 -0
  899. package/test/routes/admin/email.test.js +116 -0
  900. package/test/routes/admin/firestore-query.test.js +206 -0
  901. package/test/routes/admin/firestore.test.js +129 -0
  902. package/test/routes/admin/infer-contact.test.js +220 -0
  903. package/test/routes/admin/notification.test.js +199 -0
  904. package/test/routes/admin/post-convert-image.test.js +159 -0
  905. package/test/routes/admin/post-deploy-flag.test.js +81 -0
  906. package/test/routes/admin/post-download-error.test.js +90 -0
  907. package/test/routes/admin/post-resize-image.test.js +185 -0
  908. package/test/routes/admin/post.test.js +369 -0
  909. package/test/routes/admin/repo-content.test.js +223 -0
  910. package/test/routes/admin/stats.test.js +114 -0
  911. package/test/routes/admin/users-disable.test.js +63 -0
  912. package/test/routes/admin/users-list.test.js +71 -0
  913. package/test/routes/content/post.test.js +60 -0
  914. package/test/routes/general/uuid.test.js +133 -0
  915. package/test/routes/health.test.js +110 -0
  916. package/test/routes/marketing/campaign.test.js +184 -0
  917. package/test/routes/marketing/contact.test.js +416 -0
  918. package/test/routes/marketing/email-preferences.test.js +293 -0
  919. package/test/routes/marketing/push-send.test.js +34 -0
  920. package/test/routes/marketing/webhook-forward.test.js +63 -0
  921. package/test/routes/marketing/webhook.test.js +641 -0
  922. package/test/routes/payments/_route-harness.js +145 -0
  923. package/test/routes/payments/cancel-provider-errors.test.js +227 -0
  924. package/test/routes/payments/cancel-skip-guards.test.js +126 -0
  925. package/test/routes/payments/cancel-trialing.test.js +428 -0
  926. package/test/routes/payments/cancel.test.js +165 -0
  927. package/test/routes/payments/dedup-race.test.js +186 -0
  928. package/test/routes/payments/discount.test.js +82 -0
  929. package/test/routes/payments/dispute-alert.test.js +324 -0
  930. package/test/routes/payments/intent-discount-amounts.test.js +550 -0
  931. package/test/routes/payments/intent-discount-coupons.test.js +293 -0
  932. package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
  933. package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
  934. package/test/routes/payments/intent-zero-total.test.js +260 -0
  935. package/test/routes/payments/intent.test.js +413 -0
  936. package/test/routes/payments/plan.test.js +367 -0
  937. package/test/routes/payments/portal-return-url.test.js +107 -0
  938. package/test/routes/payments/portal.test.js +94 -0
  939. package/test/routes/payments/refund-one-time.test.js +254 -0
  940. package/test/routes/payments/refund-paypal-proration.test.js +190 -0
  941. package/test/routes/payments/refund.test.js +181 -0
  942. package/test/routes/payments/trial-eligibility.test.js +73 -0
  943. package/test/routes/payments/uncancel.test.js +270 -0
  944. package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
  945. package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
  946. package/test/routes/payments/webhook-test-provider.test.js +156 -0
  947. package/test/routes/payments/webhook.test.js +115 -0
  948. package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
  949. package/test/routes/payments/winback.test.js +597 -0
  950. package/test/routes/test/authenticate.test.js +79 -0
  951. package/test/routes/test/redirect.test.js +66 -0
  952. package/test/routes/test/reset-account.test.js +126 -0
  953. package/test/routes/test/roster.test.js +101 -0
  954. package/test/routes/test/schema.test.js +556 -0
  955. package/test/routes/test/usage.test.js +374 -0
  956. package/test/routes/user/api-keys.test.js +158 -0
  957. package/test/routes/user/connections-grant.test.js +759 -0
  958. package/test/routes/user/connections-identity.test.js +260 -0
  959. package/test/routes/user/connections-log-privacy.test.js +206 -0
  960. package/test/routes/user/connections-refresh-lease.test.js +696 -0
  961. package/test/routes/user/connections-return.test.js +261 -0
  962. package/test/routes/user/connections-uid.test.js +516 -0
  963. package/test/routes/user/delete.test.js +138 -0
  964. package/test/routes/user/feedback.test.js +106 -0
  965. package/test/routes/user/orders.test.js +233 -0
  966. package/test/routes/user/sessions.test.js +231 -0
  967. package/test/routes/user/settings-validate.test.js +84 -0
  968. package/test/routes/user/signup-emails.test.js +113 -0
  969. package/test/routes/user/signup-location.test.js +84 -0
  970. package/test/routes/user/signup-log-privacy.test.js +112 -0
  971. package/test/routes/user/signup.test.js +579 -0
  972. package/test/routes/user/subscription.test.js +101 -0
  973. package/test/routes/user/token.test.js +111 -0
  974. package/test/routes/user/user.test.js +158 -0
  975. package/test/routes/verts/cache.test.js +78 -0
  976. package/test/routes/verts/click-destination.test.js +76 -0
  977. package/test/routes/verts/crud.test.js +194 -0
  978. package/test/routes/verts/redirect.test.js +147 -0
  979. package/test/routes/verts/selection.test.js +237 -0
  980. package/test/routes/verts/serve.test.js +185 -0
  981. package/test/routes/verts/unit-document.test.js +117 -0
  982. package/test/rules/_environment.js +66 -0
  983. package/test/rules/brand-merge.test.js +195 -0
  984. package/test/rules/field-helpers.test.js +309 -0
  985. package/test/rules/notifications.test.js +577 -0
  986. package/test/rules/payments-carts.test.js +406 -0
  987. package/test/rules/sessions.test.js +170 -0
  988. package/test/rules/user.test.js +517 -0
  989. package/test/rules/verts.test.js +101 -0
  990. package/test/security/fetch-log-secrets.test.js +124 -0
  991. package/test/security/primitives.test.js +105 -0
  992. package/test/security/repo-pinning.test.js +77 -0
  993. package/test/stripe-live/subscription-lifecycle.test.js +141 -0
@@ -0,0 +1,459 @@
1
+ # Email System
2
+
3
+ Unified MJML-based email rendering for transactional, marketing, and newsletter emails. All email types go through the same pipeline: **prepare → render (MJML) → deliver (SendGrid)**. No SendGrid dynamic templates (`d-xxx` IDs) — everything is rendered server-side.
4
+
5
+ ## Architecture
6
+
7
+ ### Pipeline Overview
8
+
9
+ ```
10
+ Caller (route/transition/cron)
11
+ → prepare.js (shared: brand, sender, signoff, content, categories, unsubscribe URL)
12
+ → templates/index.js (resolve template by name)
13
+ → template.build({ data, theme }) → MJML string
14
+ → mjml-template.js (compile MJML → email-safe HTML, UTM-tag all links)
15
+ → transactional/index.js (recipients, dedup, SendGrid Mail Send)
16
+ OR marketing/index.js (audience, Single Send)
17
+ ```
18
+
19
+ ### Entry Points
20
+
21
+ | Context | API | Delivers via |
22
+ |---|---|---|
23
+ | Transactional (individual) | `Manager.Email(ctx).send(settings)` | SendGrid Mail Send |
24
+ | Marketing (campaign) | `Manager.Email(ctx).sendCampaign(settings)` | SendGrid Single Send + Beehiiv |
25
+ | Newsletter (generated) | `generators/newsletter.js` → `renderNewsletter()` | Same as marketing |
26
+
27
+ ### Shared Preparation (`prepare.js`)
28
+
29
+ Both transactional and marketing paths share the same preparation layer:
30
+
31
+ | Function | Purpose |
32
+ |---|---|
33
+ | `resolveBrand(Manager)` | Clones brand config, sanitizes images (SVG→PNG via CDN naming) |
34
+ | `resolveSender({ sender, from, group }, brand, brandDomain, Manager)` | Resolves sender from/display-name by category key, and the ASM group id from config (`marketing.campaigns.providers.sendgrid.groups.<key>`) — see [Unsubscribe groups](#unsubscribe-groups) |
35
+ | `renderContent({ content, html, trusted }, utmOptions)` | Markdown→HTML via markdown-it, applies UTM link tagging. Raw HTML in `content` is DISABLED unless `trusted` is set — see [Content trust](#content-trust) |
36
+ | `resolvePerson(brand)` | The `brand.contact.person` identity for personal email. Throws 400 when unconfigured — see [Identity is config, or it is an error](#identity-is-config-or-it-is-an-error) |
37
+ | `resolveSignoff(signoff, brand)` | Fills personal signoff details (name, headshot, URL) from `brand.contact.person` when `type: 'personal'` |
38
+ | `buildCategories(type, brandId, extra)` | Builds categories array: `['transactional', brandId, ...extra]` |
39
+ | `buildUnsubscribeUrl({ email, groupId, template, websiteUrl })` | HMAC-signed one-click unsubscribe URL |
40
+ | `buildTemplateData({ brand, subject, ... })` | Deep-merges system defaults with caller data into the template data tree |
41
+ | `render({ brand, template, data, utm })` | Compiles MJML template to email-safe HTML via `renderEmail()` |
42
+
43
+ ### Identity is config, or it is an error
44
+
45
+ Nothing in the email path carries a built-in human or company identity. Every name, face, link and audit address comes from the merged config, and a missing one **fails loudly instead of falling back** — a silent default would sign a brand's mail as someone else.
46
+
47
+ | Surface | Config key | Missing behavior |
48
+ |---|---|---|
49
+ | Personal signoff (name, headshot, link) | `brand.contact.person.{name,image,url,urlText}` | `prepare.resolvePerson()` **throws 400** when `name` is unset and a `personal` signoff was requested |
50
+ | Personal copy in email bodies ("I'm Jane, the founder…") | `brand.contact.person.firstName` | Defaults to the first word of the configured `person.name` — derived from the brand's own value, never a framework one |
51
+ | Email footer parent entity | `brand.company` | Falls back to `brand.name` (the documented schema chain) |
52
+ | Email footer parent wordmark | `brand.images.companyWordmark` | The wordmark block is **omitted** — never another company's logo |
53
+ | Audit BCCs on `copy: true` sends | `brand.contact.carbonCopy` (`[{ email, name }]`) | No BCCs. A listed entry missing `email` throws 400 |
54
+
55
+ `copy: true` still CCs the brand's own `brand.contact.email` — that is the brand copying itself and needs no extra config.
56
+
57
+ A team signoff (the default) needs none of this. Only `signoff.type: 'personal'` requires a configured person; the signup welcome/nudge/checkup emails are the in-framework users of that path, and each one's send is individually caught and logged, so an unconfigured brand logs a loud error per email rather than failing signup.
58
+
59
+ ### Content trust
60
+
61
+ Email bodies arrive from two very different places, so `renderContent()` has two lanes and the SAFE one is the default:
62
+
63
+ | Lane | Who uses it | markdown-it |
64
+ |---|---|---|
65
+ | **Untrusted** (default) | AI-authored campaign/newsletter bodies, user-submitted fields, anything arriving over a route or the MCP `send_email`/`create_campaign` tools | `html: false` — smuggled `<script>`/`<img onerror>` renders as inert text |
66
+ | **Trusted** (`trustedContent: true` on the send settings) | First-party callers that hand-build markup: the dispute alert (`events/firestore/payments-disputes/on-write.js`) and the newsletter report email (`generators/newsletter.js`) | `html: true` |
67
+
68
+ Rules for the trusted lane:
69
+
70
+ - A caller may set `trustedContent: true` ONLY for markup it authored itself — and only an INTERNAL caller may set it at all, see [Internal-only send fields](#internal-only-send-fields).
71
+ - Every third-party or AI-authored value interpolated into that markup must go through `escapeHtml()` (`constants.js`) first — otherwise the trusted lane becomes an injection lane. Both current trusted callers do this for their webhook/AI values.
72
+ - Every URL interpolated into an `href` must go through `safeUrl()` (`constants.js`), not `escapeHtml()` — see [Link schemes](#link-schemes).
73
+ - `data.content.html` is a raw-HTML passthrough by declaration — INTERNAL callers only, see [Internal-only send fields](#internal-only-send-fields).
74
+
75
+ Newsletter template rendering (`generators/lib/templates/newsletter-shared.js`) is independently `html: false` — those bodies are always AI-authored — and escapes every AI field it interpolates via the shared `escapeHtml()`.
76
+
77
+ ### Internal-only send fields
78
+
79
+ Four send/campaign fields hand the renderer raw HTML, or the trust to render it. Each is a real first-party lane AND a complete bypass of the escaped one, so first-party callers keep all four and **no caller arriving over the API may set any of them**. Ian's call on [#90](https://github.com/Omega-JS-Stack/omega/issues/90), extended to `html` on [#125](https://github.com/Omega-JS-Stack/omega/issues/125).
80
+
81
+ | Field | What it does | Its internal user |
82
+ |---|---|---|
83
+ | `data.content.html` | Skips markdown — the body lands in the inbox as live markup | Pre-rendered first-party bodies |
84
+ | `html` (top level) | Replaces the rendered MJML document outright (`transactional/index.js` build) | Callers that hand over a complete document |
85
+ | `contentHtml` (top level) | Same, on the campaign lane — read AHEAD of the escaped renderer (`marketing/index.js`), and it persists into the stored campaign doc that cron sends later | `generators/newsletter.js` |
86
+ | `trustedContent` | Flips the body renderer to `html: true` (raw HTML *and* `javascript:` hrefs survive) | The dispute alert + the newsletter report email |
87
+
88
+ | Lane | All four fields |
89
+ |---|---|
90
+ | Internal callers (generators, transition handlers, cron, auth hooks) | Accepted — unchanged |
91
+ | `POST /admin/email` (the surface behind the MCP `send_email` tool) | **Blocked** — the route schema strips them (belt); the guard 400s any that slip past (braces) |
92
+ | `POST` / `PUT /marketing/campaign` (the MCP `create_campaign` / `update_campaign` tools) | **Blocked** — same belt-and-braces pair |
93
+
94
+ The check is one shared helper over one field table: `prepare.internalOnlyFieldFault(settings)` returns a coded-400 permanent fault (naming the offending field and the rule) when the caller's settings carry any of them, else null. In practice the route schemas strip these keys first, so over HTTP the guard is the braces behind that belt — it fires only for a lane whose schema misses a field. Every caller-facing email lane runs its resolved settings through it BEFORE handing them to the library — and, on the campaign routes, before `buildCampaignDoc()`, which blacklists doc-level fields rather than allowlisting and would otherwise persist whatever the caller sent. An external lane added later must do the same. Rejection is on the FIELD's presence, not its contents: an external caller has no legitimate reason to send any of these keys at all, and the rejection is logged by field name (never the payload).
95
+
96
+ Three of the four (`html`, `contentHtml`, `trustedContent`) are also undeclared on the route schemas, and a zod object strips unknown keys — that strip is the belt, this guard the braces, and it is the guard that holds the moment a schema gains the field or a consumer route forwards raw settings. `data.content.html` has no belt: `data` is a schema passthrough, so the guard is its only stop.
97
+
98
+ `html` was a declared field on the `POST /admin/email` schema and a documented MCP `send_email` parameter until [#125](https://github.com/Omega-JS-Stack/omega/issues/125); Ian ruled the admin/MCP lane matches the API lane exactly, so both the schema field and the tool parameter are gone and the guard covers it like the rest.
99
+
100
+ Markdown in `data.content.message` is the external caller's lane, and it renders through the untrusted (escaped) renderer.
101
+
102
+ ### Link schemes
103
+
104
+ An `href` is not made safe by `escapeHtml()` — `javascript:alert(1)` survives escaping intact, and third-party or AI-authored URLs (a dispute alert's `stripeUrl`, a newsletter source's `url`, an AI-written CTA) land in exactly that position. Every URL interpolated into an href goes through `safeUrl()` (`constants.js`) instead:
105
+
106
+ - **Allowed**: `http:`, `https:`, `mailto:`, and relative/anchor values (no scheme — they cannot carry script). The kept value is still `escapeHtml()`d, which also closes attribute-breakout via a quote in the URL.
107
+ - **Dropped**: everything else (`javascript:`, `data:`, `vbscript:`, `file:`), including the obfuscated forms clients still execute — whitespace, control characters, and zero-width/format characters inside the scheme (`java<TAB>script:`, `java<ZWSP>script:`) are all stripped before the scheme is matched. The href comes back empty — the link goes dead — and the drop is logged. It does not throw: one bad URL inside a webhook or AI payload must not take down the whole alert email.
108
+
109
+ Markdown LINKS need no extra guard — markdown-it's own `validateLink` blocks these schemes in `[text](url)` syntax on both lanes. It is not a guard on the trusted lane's raw HTML: an `<a href="javascript:...">` written directly into a `trustedContent` body is markup, not markdown link syntax, so `validateLink` never sees it. That anchor is the caller's responsibility — hence the `safeUrl()` rule above, and hence `trustedContent` being internal-only.
110
+
111
+ ### Transactional Pipeline (`transactional/index.js`)
112
+
113
+ Steps inside `build()`:
114
+
115
+ 1. **Brand + sender** — `prepare.resolveBrand()` + `prepare.resolveSender()`
116
+ 2. **Recipients** — normalize to `{ email, name }`, UID lookup from Firestore, dedup across to/cc/bcc
117
+ 3. **Content** — `prepare.renderContent()` (markdown→HTML if needed)
118
+ 4. **Template data** — `prepare.buildTemplateData()` merges brand/signoff/email/categories with caller data
119
+ 5. **Render** — `prepare.render()` → MJML template → compiled HTML → UTM-tag all links
120
+ 6. **Assemble** — SendGrid Mail Send object with `content: [{ type: 'text/html', value: html }]`
121
+
122
+ After `build()`, `send()` delivers via SendGrid, handles scheduled sends (>71h → queue), and persists an audit trail to `emails/{messageId}`.
123
+
124
+ ### Marketing Pipeline (`marketing/index.js`)
125
+
126
+ `_sendCampaignSendGrid()` follows the same prepare → render → deliver pattern. Content comes from `data.content.message` (markdown) — same location as transactional callers. Key difference: audience targeting uses brand-scoped dynamic segments (see [marketing-campaigns.md](marketing-campaigns.md)).
127
+
128
+ ### Email Validation Pipeline (`validation.js`)
129
+
130
+ All marketing contact operations (`add`, `sync`) pass through `validate()` before reaching providers. Checks run in order; the first failure short-circuits. (The library consent gate runs even earlier — a user with `consent.marketing.status === 'revoked'` returns `{ blocked: 'consent', email }` before validation; see [consent.md](consent.md#email-library-consent-gate).)
131
+
132
+ | # | Check | What it catches | Cost | Default |
133
+ |---|---|---|---|---|
134
+ | 1 | `format` | Regex: must have `@`, domain, no spaces | Free | Yes |
135
+ | 2 | `disposable` | ~7k known disposable domains (vendor list + custom additions) | Free | Yes |
136
+ | 3 | `corporate` | Social/corporate domains (instagram.com, facebook.com, etc.) | Free | Yes |
137
+ | 4 | `localPart` | Junk local parts (test, noreply, all-numeric, `_test.*`) | Free | Yes |
138
+ | 5 | `typo` | Common domain misspellings via prefix match (`gamil.`, `gmai.`, `aol.con`, `gmail.cok`, etc.) | Free | Yes |
139
+ | 6 | `dns` | No MX record, null MX (RFC 7505), loopback MX, domain not found | Free | Opt-in |
140
+ | 7 | `mailbox` | SMTP mailbox verification via NeverBounce or ZeroBounce | Paid | Opt-in |
141
+
142
+ - **`DEFAULT_CHECKS`** = checks 1–5 (all free, run on every `mailer.add()`/`mailer.sync()` call)
143
+ - **`ALL_CHECKS`** = checks 1–7 (used at signup to include paid mailbox verification)
144
+ - The `dns` check is opt-in (not in DEFAULT_CHECKS) because it's async/slower — include it for bulk validation
145
+ - The `typo` check uses prefix matching (`"gamil."` catches `gamil.com`, `gamil.con`, `gamil.co`) — see `data/typo-domains.js`
146
+ - Custom disposable domains go in `data/custom-disposable-domains.json` (not the vendor list)
147
+ - Run `npx omega test framework:email/validation-cases` to verify all checks against the address corpus
148
+
149
+ ### The disposable-domain dataset: seed + refresh cache
150
+
151
+ The vendor list is a committed **seed** plus a gitignored **refresh cache**, owned by `libraries/email/disposable-domains.js`. A refresh can never dirty the git tree.
152
+
153
+ | | Path | Written by |
154
+ |---|---|---|
155
+ | Seed (committed) | `src/manager/libraries/email/data/disposable-domains.json` | `node scripts/promote-disposable-domains.js` — nothing else, ever |
156
+ | Cache (gitignored) | `.cache/email/disposable-domains.json` | `node scripts/update-disposable-domains.js` (also the `npm prepare` before-hook) |
157
+
158
+ `load()` reads the cache when it is present and parseable, else the seed — so an offline clone, a CI box with no network, and a deployed function (which ships the seed and no cache) all resolve the same committed baseline.
159
+
160
+ **To advance the committed baseline**: `node scripts/update-disposable-domains.js` to refresh the cache, then `node scripts/promote-disposable-domains.js` to copy it over the seed, then review and commit the diff.
161
+
162
+ ## Data Contract
163
+
164
+ The template receives one `data` object with a clear separation of concerns:
165
+
166
+ - **`data.content`** — template-specific payload. **Callers provide this.** What goes inside depends on the template.
167
+ - **`data.signoff`** — shared across templates. **Callers provide this** (defaults to team if omitted).
168
+ - **`data.brand`** / **`data.email`** / **`data.personalization`** — **system-injected by `prepare.js`**. Callers never touch these.
169
+
170
+ `trustedContent: true` (a top-level send setting, not part of `data`) opts the body out of HTML escaping — first-party markup only, see [Content trust](#content-trust).
171
+
172
+ Every caller — transactional, marketing, transition handler — passes data the same way:
173
+
174
+ ```js
175
+ await email.send({
176
+ template: 'card',
177
+ subject: 'Welcome!',
178
+ to: 'user@example.com',
179
+ sender: 'hello',
180
+ categories: ['account/welcome'],
181
+ data: {
182
+ content: { title: 'Welcome!', message: '# Hello!\n\nMarkdown here.' },
183
+ signoff: { type: 'personal' },
184
+ },
185
+ });
186
+ ```
187
+
188
+ ### Per-template `data.content` shapes
189
+
190
+ | Template | `data.content` fields |
191
+ |---|---|
192
+ | **card** | `{ title, message, button: { text, url } }` |
193
+ | **plain** | `{ greeting, message, link: { url, text }, signoff }` |
194
+ | **order** | `{ event, id, type, unified, _computed, provider }` |
195
+ | **feedback** | (none — self-contained) |
196
+
197
+ Marketing campaigns add `discountCode` to `data.content`:
198
+ ```js
199
+ data: {
200
+ content: {
201
+ title: 'Summer Sale!',
202
+ message: 'Markdown with **{discount.code}**...',
203
+ button: { text: 'Upgrade Now →', url: '{brand.url}/pricing' },
204
+ discountCode: 'SUMMER15',
205
+ },
206
+ }
207
+ ```
208
+
209
+ Template variables (`{brand.name}`, `{discount.code}`, `{holiday.name}`, etc.) are resolved across the entire settings object before rendering.
210
+
211
+ ## Template System
212
+
213
+ ### Template Registry (`templates/index.js`)
214
+
215
+ Two registries, two resolve functions:
216
+
217
+ ```js
218
+ resolveEmailTemplate('card') // → card | plain | order | feedback
219
+ resolveNewsletterTemplate('clean') // → clean | editorial | field-report
220
+ ```
221
+
222
+ No aliases. Callers use direct template names. Unknown email templates fall back to `card` with a console warning.
223
+
224
+ ### Template Builder Signature
225
+
226
+ Every email template exports `{ build, meta }`:
227
+
228
+ ```js
229
+ function build({ data, theme, templateName }) {
230
+ return '<mjml>...</mjml>';
231
+ }
232
+ const meta = { name: 'card', description: '...' };
233
+ module.exports = { build, meta };
234
+ ```
235
+
236
+ ### Composable Base Blocks (`base.js`)
237
+
238
+ All templates compose from shared building blocks. `skeleton()` is required; everything else is opt-in.
239
+
240
+ | Block | Purpose | Used by |
241
+ |---|---|---|
242
+ | `skeleton(opts, content)` | Required wrapper. `<mjml>` + `<mj-head>` (title, preview, styles) + `<mj-body>` + hidden ASM tags + hidden category tags | All templates |
243
+ | `logo(brand, theme)` | Centered brandmark image (or fallback text) | card, order, feedback |
244
+ | `cardWrapper(content)` | White card with border + 16px rounded corners | card, order, feedback |
245
+ | `signoff(data, theme)` | Team ("The Brand Team") or personal (headshot + name + link) | card, order |
246
+ | `button(btn)` | Dark CTA button | card, order |
247
+ | `footer(brand, email)` | ITW wordmark, footer text, links (account/terms/privacy/unsub), copyright, address | card, order, feedback |
248
+
249
+ ### Hidden Tags (inside `skeleton()`)
250
+
251
+ Every email includes hidden elements that SendGrid and email clients process but users don't see:
252
+
253
+ - **ASM tags**: `<%asm_group_unsubscribe_raw_url%>` + `<%asm_preferences_raw_url%>` — suppress SendGrid's auto-inserted unsubscribe text
254
+ - **Category tags**: `category=transactional`, `category=order/confirmation`, etc. — used for email sorting/filtering
255
+
256
+ ### Email Templates
257
+
258
+ #### `card` (default)
259
+
260
+ The workhorse. White card on gray background with logo, title, markdown body, optional CTA button, signoff, and full footer. Used for: welcome emails, account notifications, data requests, general transactional, marketing campaigns.
261
+
262
+ #### `plain`
263
+
264
+ Looks like a regular email from a person. No logo, no card, no branding. Full-width (`<mj-body width="100%">`). Just message + signoff + minimal gray footer with unsubscribe link. Used for: personal outreach, plain notifications.
265
+
266
+ #### `order`
267
+
268
+ Handles ALL 9 order event types in one template. Event from `data.content.event`. Componentized into sections:
269
+
270
+ | Section | Purpose |
271
+ |---|---|
272
+ | `_header()` | Emoji + title + subtitle per event type |
273
+ | `_summary()` | Product/price/discount/total table (only for `SUMMARY_EVENTS`) |
274
+ | `_details()` | Date, provider, frequency, account email |
275
+ | `_explanation()` | Conditional paragraphs: trial notice, promo code, cancellation reason, etc. |
276
+ | `_ctaButton()` | CTA pointing to dashboard/billing/pricing depending on event |
277
+ | `_helpText()` | "Questions? Contact support" |
278
+
279
+ **Event types:**
280
+
281
+ | Event | Fired by | Emoji |
282
+ |---|---|---|
283
+ | `confirmation` | new-subscription, purchase-completed | :tada: |
284
+ | `payment-failed` | payment-failed transition | :warning: |
285
+ | `payment-recovered` | payment-recovered transition | :white_check_mark: |
286
+ | `cancellation-requested` | cancellation-requested transition | :wave: |
287
+ | `cancelled` | subscription-cancelled transition | :x: |
288
+ | `plan-changed` | plan-changed transition | :arrows_counterclockwise: |
289
+ | `refunded` | payment-refunded transition | :moneybag: |
290
+ | `trial-ending` | (future) trial-ending cron | :hourglass: |
291
+ | `abandoned-cart` | abandoned-carts cron | :shopping_cart: |
292
+
293
+ #### `feedback`
294
+
295
+ Rating faces (dislike/neutral/like/love) with gift card incentive. Four clickable faces linking to a feedback URL with a `rating` query param — unicode glyphs, not hosted images, so the mail fetches nothing from a third-party CDN. Invisible placeholder labels on empty cells for vertical alignment. Used by the signup post-onboarding feedback email.
296
+
297
+ ### Newsletter Templates
298
+
299
+ Newsletter templates are separate from email templates — different input shape, different registry. See [marketing-campaigns.md](marketing-campaigns.md) for the full newsletter system.
300
+
301
+ | Template | Style |
302
+ |---|---|
303
+ | `clean` | Simple, minimal |
304
+ | `editorial` | Full-width hero sections, editorial tone |
305
+ | `field-report` | Structured dispatch format |
306
+
307
+ ## UTM Auto-Tagging
308
+
309
+ `utm.js` → `tagLinks()` auto-tags **all HTTP/HTTPS links** in email HTML — not just brand-domain links. Applied at two levels:
310
+
311
+ 1. **Content rendering** — `prepare.renderContent()` tags links in the markdown→HTML body
312
+ 2. **MJML compilation** — `renderEmail()` tags links in the compiled template HTML (CTA buttons, footer links, signoff links, etc.)
313
+
314
+ Default UTM params (auto-derived, no manual setup needed):
315
+
316
+ | Param | Source | Example |
317
+ |---|---|---|
318
+ | `utm_source` | Brand ID | `somiibo` |
319
+ | `utm_medium` | Always `email` | `email` |
320
+ | `utm_campaign` | First caller category (transactional) or campaign name (marketing) | `account_welcome`, `summer_sale_free_users` |
321
+ | `utm_content` | `transactional` or `marketing` | `transactional` |
322
+
323
+ Existing UTM params on a URL are never overwritten. Callers can pass `utm: { utm_term: '...' }` for additional params. Values are sanitized to lowercase alphanumeric + underscores.
324
+
325
+ ## Transition Handler Email Convention
326
+
327
+ All payment transition handlers pass `template: 'order'` + `data.content` to the `send-email` utility:
328
+
329
+ ```js
330
+ // In transitions/subscription/new-subscription.js:
331
+ sendOrderEmail({
332
+ template: 'order',
333
+ subject: 'Your order #...',
334
+ categories: ['order/confirmation'],
335
+ data: {
336
+ content: { event: 'confirmation', ...order, _computed: { ... } },
337
+ },
338
+ });
339
+ ```
340
+
341
+ The order template reads `data.content` for all rendering decisions. No per-event template files — the single `order.js` handles everything.
342
+
343
+ ### Personalization
344
+
345
+ Recipient display name in Gmail comes from the `to` field: `{ email: 'user@example.com', name: 'Taylor Trial' }`. In the email body, templates use `data.personalization.name` for greetings like "Hey Taylor".
346
+
347
+ Names are resolved during recipient normalization in the transactional pipeline — the user's `personal.name.first` from their Firestore doc is used when available.
348
+
349
+ ## Sender Categories
350
+
351
+ Defined in `constants.js` → `SENDERS`. Each category auto-resolves a from address, display name, and unsubscribe-group KEY:
352
+
353
+ | Category | From | Display Name | Group key |
354
+ |---|---|---|---|
355
+ | `orders` | `orders@{domain}` | Orders at {Brand} | `orders` |
356
+ | `hello` | `hello@{domain}` | {Brand} | `hello` |
357
+ | `account` | `account@{domain}` | {Brand} Account | `account` |
358
+ | `marketing` | `marketing@{domain}` | {Brand} | `marketing` |
359
+ | `security` | `security@{domain}` | {Brand} Security | `security` |
360
+ | `newsletter` | `newsletter@{domain}` | {Brand} Newsletter | `newsletter` |
361
+ | `internal` | `alerts@{domain}` | {Brand} Alerts | `internal` |
362
+
363
+ ## Unsubscribe groups
364
+
365
+ A SendGrid unsubscribe (ASM) group id belongs to the SendGrid ACCOUNT that created
366
+ it, so ids are never code. `constants.js` carries `GROUP_KEYS` — the seven keys
367
+ above — and nothing else; the id of each lives in the brand's own config at
368
+ `marketing.campaigns.providers.sendgrid.groups.<key>`
369
+ ([#649](https://github.com/Omega-JS-Stack/omega/issues/649)).
370
+
371
+ - The manager's campaigns service provisions the groups (matched by NAME, so
372
+ sibling brands on one account converge on the same ids) and writes each id back
373
+ into `config/omega.json5`. Nothing here creates them.
374
+ - `prepare.resolveSender()` reads the id at build time. A missing one throws a
375
+ coded-400 naming the config path: it means the manage walk never ran, and
376
+ sending anyway would attach a group from somebody else's account.
377
+ - A caller may pass `group:` explicitly — a KEY resolves through config, a raw
378
+ numeric id is used as-is.
379
+
380
+ ## Testing
381
+
382
+ ### The testing-mode capture (the SendGrid stand-in)
383
+
384
+ Outside extended mode, `Transactional.send()` **records** the email instead of delivering it and returns `{ status: 'captured' }` ([#774](https://github.com/Omega-JS-Stack/omega/issues/774)). The seam sits past `build()` — after the brand, the recipients, the template data and the MJML render, before SendGrid is even required — so a broken template or an unconfigured signoff fails a test rather than reaching production, and the audit trail and the `admin/email` analytics event (both facts about a DELIVERY) are not written.
385
+
386
+ **The gate is the mailer's, not the caller's.** `ctx.isTesting() && !TEST_EXTENDED_MODE` is asked in exactly one place (`isCapturing()`), the way the marketing library gates `add`/`sync`/`remove` at the SSOT level. Do not add an `isTesting()` check around a new `send()` call — the seam already covers it. What the seam does NOT cover: a send scheduled past `SEND_AT_LIMIT` is queued to `emails-queue` before the seam is reached (assert it there), and extended mode still sends real mail.
387
+
388
+ **The store is a file:** `<projectDir>/.temp/test-emails.jsonl`, one JSON record per line, beside `test-mode.json` and resolved the same way (the parent of `Manager.cwd`). A file rather than a `_test/emails` Firestore collection because a test drives the mailer from three places and only a file serves all three — the emulator's function worker (another process from the test runner, so an in-memory array is impossible), the test-runner process itself, and a plain-node case with no emulator and therefore no Firestore to read. It also needs no rules, no index and no cleanup lane, and `.temp/` is already gitignored. Cross-process appends stay atomic by keeping every record under `PIPE_BUF` (the summary absorbs the trim).
389
+
390
+ **The helpers are test-only** (`src/test/utils/email-capture.js`, never a framework export, exactly like `test-mode-file.js`):
391
+
392
+ ```javascript
393
+ const capture = require('../../dist/test/utils/email-capture.js');
394
+
395
+ capture.clearCaptured(Manager); // before the act
396
+ await http.as('signup-emails').post('backend-manager/user/signup', {});
397
+ capture.readCaptured(Manager); // [{ to, template, subject, summary, sendAt }]
398
+ ```
399
+
400
+ The record's `summary` is the rendered body reduced to visible text — where a test finds the product name a receipt exists to state. Field table and the fire-and-forget caveat: [test-framework.md](test-framework.md#testing-mode-email-capture--how-a-test-reads-what-was-sent).
401
+
402
+ All email tests live under `test/email/`, mirroring the source at `src/manager/libraries/email/`:
403
+
404
+ | Test file | What it tests | Extended? |
405
+ |---|---|---|
406
+ | `templates.js` | MJML rendering for all 4 email templates (11 tests) | No |
407
+ | `testing-capture.js` | The [testing-mode capture](#the-testing-mode-capture-the-sendgrid-stand-in): the gate, the store, the summary, the mailer's seam (6 tests) | No |
408
+ | `transactional.js` | Transactional email building (assertions on output shape) | No |
409
+ | `validation.js` | Email format/disposable/corporate/local-part/typo/dns checks (52 tests) | No |
410
+ | `transactional-send.js` | Single transactional email send via SendGrid | Yes |
411
+ | `campaign-send.js` | Marketing campaign send with title + CTA + discount code | Yes |
412
+ | `feedback-and-plain-send.js` | Feedback + plain template visual test sends | Yes |
413
+ | `newsletter-templates.js` | Newsletter MJML rendering (16 tests) | No |
414
+ | `newsletter-generate.js` | Full AI newsletter generation pipeline (5min timeout) | Yes |
415
+ | `marketing-lifecycle.js` | Contact lifecycle (add/sync/remove) | Yes |
416
+ | `consent-lifecycle.js` | Consent webhook round-trip | Yes |
417
+ | `render-content.js` | The [content-trust](#content-trust) render lanes (15 tests) | No |
418
+ | `identity.js` | Config-driven identity + the loud failures (16 tests) | No |
419
+ | `validation-cases.js` | Address corpus for the free checks + NeverBounce parsing (69 tests) | No |
420
+ | `sanitize-images.js` | Brand-image absolutization (5 tests) | No |
421
+ | `marketing/consent-gate.js` | The marketing consent gate (21 tests) | No |
422
+ | `unsubscribe-groups.js` | [Unsubscribe groups](#unsubscribe-groups) resolve from config, and a missing id fails loudly (7 tests) | No |
423
+
424
+ Extended tests (`TEST_EXTENDED_MODE`) send real emails to `_test-*@{domain}` addresses. See [test-framework.md](test-framework.md) for the full test framework reference.
425
+
426
+ The last six are pure plain-node units — no emulator, no providers, no network — but they run in the discovered suite like everything else (`npx omega test framework:email`).
427
+
428
+ ### Test recipient convention
429
+
430
+ All extended email tests send to `_test-<purpose>@{domain}` addresses (e.g. `_test-email-send@somiibo.com`). This keeps test emails separate from real user traffic and makes filtering easy.
431
+
432
+ ## Key Files
433
+
434
+ | Purpose | File |
435
+ |---|---|
436
+ | Shared preparation | `src/manager/libraries/email/prepare.js` |
437
+ | Transactional pipeline | `src/manager/libraries/email/transactional/index.js` |
438
+ | Marketing pipeline | `src/manager/libraries/email/marketing/index.js` |
439
+ | MJML compiler (both email + newsletter) | `src/manager/libraries/email/generators/lib/mjml-template.js` |
440
+ | Template registry | `src/manager/libraries/email/generators/lib/templates/index.js` |
441
+ | Base blocks | `src/manager/libraries/email/generators/lib/templates/base.js` |
442
+ | Card template | `src/manager/libraries/email/generators/lib/templates/card.js` |
443
+ | Plain template | `src/manager/libraries/email/generators/lib/templates/plain.js` |
444
+ | Order template (9 events) | `src/manager/libraries/email/generators/lib/templates/order.js` |
445
+ | Feedback template | `src/manager/libraries/email/generators/lib/templates/feedback.js` |
446
+ | UTM link tagging | `src/manager/libraries/email/utm.js` |
447
+ | Constants (senders, groups, fields, segments) | `src/manager/libraries/email/constants.js` |
448
+ | Email validation | `src/manager/libraries/email/validation.js` |
449
+ | Typo domain prefixes | `src/manager/libraries/email/data/typo-domains.js` |
450
+ | Custom disposable domains | `src/manager/libraries/email/data/custom-disposable-domains.json` |
451
+ | Disposable-domain seed/cache contract | `src/manager/libraries/email/disposable-domains.js` |
452
+ | NeverBounce provider | `src/manager/libraries/email/validation-provider-neverbounce.js` |
453
+ | Testing-mode capture (the SendGrid stand-in) | `src/test/utils/email-capture.js` |
454
+ | ZeroBounce provider | `src/manager/libraries/email/validation-provider-zerobounce.js` |
455
+ | Validation tests | `test/email/validation.test.js`, `test/email/validation-cases.test.js` |
456
+ | Content-trust test (render lanes + escaping) | `test/email/render-content.test.js` |
457
+ | Identity test (config-driven + loud failures) | `test/email/identity.test.js` |
458
+ | Seed campaigns | `src/cli/commands/setup-tests/helpers/seed-campaigns.js` |
459
+ | Transition email dispatcher | `src/manager/events/firestore/payments-webhooks/transitions/send-email.js` |
@@ -0,0 +1,93 @@
1
+ # Environment Detection
2
+
3
+ `getEnvironment()` returns exactly ONE of three mutually-exclusive, exhaustive values:
4
+
5
+ ```javascript
6
+ Manager.getEnvironment() // 'development' | 'testing' | 'production'
7
+
8
+ Manager.isDevelopment() // true ONLY in development
9
+ Manager.isTesting() // true ONLY in testing
10
+ Manager.isProduction() // true ONLY in production
11
+ ```
12
+
13
+ **The Manager is the single source of truth.** `getEnvironment()` is the ONLY function that reads the raw signals (`OMEGA_TEST_MODE` / `ENVIRONMENT` / `FUNCTIONS_EMULATOR` / `TERM_PROGRAM`). The three `is*()` checks **derive** from it live on every call — they never read raw signals themselves, so they can never disagree with `getEnvironment()`.
14
+
15
+ **the route context (ctx) forwards to the Manager.** Request handlers receive an `ctx`, so the same methods are exposed there and return identical results — call whichever is in scope:
16
+
17
+ ```javascript
18
+ ctx.getEnvironment() // === Manager.getEnvironment() — a thin forward
19
+ ctx.isTesting() // === Manager.isTesting()
20
+ ```
21
+
22
+ (An ctx always has a Manager — `init()` throws without one. The `ctx.meta.environment` field is still populated for code that reads it, but the `is*()` checks no longer depend on that snapshot.)
23
+
24
+ **Resolution order:** testing wins first, then production, else development. The three checks are mutually exclusive — exactly one is true. `isDevelopment()` is **false** during testing, and `isProduction()` is a real positive check (it is NOT `!isDevelopment()`).
25
+
26
+ ## Available helpers
27
+
28
+ | Helper | Returns |
29
+ |---|---|
30
+ | `getEnvironment()` | `'development' \| 'testing' \| 'production'` — the SSOT resolver; the only reader of raw signals. |
31
+ | `isDevelopment()` | `true` ONLY in development (local Firebase emulator / dev), and NOT testing. Derives from `getEnvironment()`. |
32
+ | `isTesting()` | `true` ONLY in testing (`OMEGA_TEST_MODE === 'true'`). **Takes precedence** — a test run is not development. |
33
+ | `isProduction()` | `true` ONLY in production (deployed Cloud Functions). A **real positive check** — NOT `!isDevelopment()`. |
34
+
35
+ ## Gating side effects — use the INTENTIONAL check
36
+
37
+ Because there are three environments, never gate a side effect on a two-value assumption. State what you mean:
38
+
39
+ ```javascript
40
+ // Production-only (skip real emails/analytics/Sentry/webhooks in dev AND testing):
41
+ if (isProduction()) { /* do the real thing */ }
42
+ if (!isProduction()) { /* skip / use the safe local behavior */ }
43
+
44
+ // Local-or-test (anything that should run in BOTH dev and testing):
45
+ if (isDevelopment() || isTesting()) { /* localhost URL, console logging, etc. */ }
46
+ ```
47
+
48
+ **Avoid** `if (!isDevelopment())` or `if (env !== 'development')` to gate production behavior — those wrongly include `testing` as production and leak real side effects (emails, analytics, Sentry) during test runs. This is the bug class that motivated the 3-value model.
49
+
50
+ ## URL helpers
51
+
52
+ ```javascript
53
+ Manager.getApiUrl() // this brand's API URL — the SSOT for calling the @omega.js/backend API
54
+ ```
55
+
56
+ **`Manager.getApiUrl()` is the one and only way to get the API URL.** It resolves to the **local** hosting emulator (`http://localhost:5002`) in development OR testing, and to production (`https://api.{domain}`) otherwise. Always call `getApiUrl()` directly — do NOT read the cached `Manager.project.apiUrl` property (it's a boot-time snapshot kept only for internal env-var export; the getter is the SSOT and always fresh). Build full endpoints by appending the path: `` `${Manager.getApiUrl()}/omega/admin/post` ``.
57
+
58
+ Resolving local in test mode is required because tests hit the local emulator — without it, internal @omega.js/backend→@omega.js/backend calls (and tests calling `getApiUrl()`) would leak to the live production server. Pass an explicit `env` arg (`getApiUrl('production')`) only to force a specific environment regardless of the current one — rarely needed, and mainly used by tests to pin a specific environment's mapping.
59
+
60
+ **Local scheme follows the https stack:** when this process runs behind the mkcert TLS proxy (`omega serve` / `omega emulator` set `OMEGA_HTTPS_PORT`), the local URLs carry `https` — `getApiUrl()` → `https://localhost:5002`, and `getWebsiteUrl()` follows the same signal for the website dev server (`https://localhost:4000`), since one mkcert install drives web and backend dev alike. Without the proxy (`--no-https` / no mkcert) both stay plain `http`. The **test runner** is the one child that never inherits `OMEGA_HTTPS_PORT`: it holds no mkcert CA, so `omega test` hands it hosting's internal plain-http port and every in-process getter answers `http://localhost:<hosting>` ([docs/test-framework.md](test-framework.md#the-stack-the-runner-child-resolves)).
61
+
62
+ > `getFunctionsUrl()` (raw Cloud Functions URL) exists for the ONE internal case that must name a specific deployed function by its raw address (`ctx.tryUrl()`). Application/route code should never need it — use `getApiUrl()`.
63
+
64
+ **Exception — parent helpers stay live:** `Manager.getParentApiUrl()` / `getParentUrl()` ALWAYS return the live production URL, even in dev/test. The parent @omega.js/backend is a real remote server with no localhost equivalent, so cross-brand parent calls are never redirected to localhost.
65
+
66
+ ## Where they live
67
+
68
+ Source: [src/manager/index.js](../src/manager/index.js). @omega.js/backend has a single Manager (no multi-context mixin like EM/UJM/BXM), so `getEnvironment()` + `is*()` + the URL helpers live directly on the Manager. The `ctx` exposes the same methods and forwards each to its Manager (`ctx.isTesting()` → `Manager.isTesting()`), so request handlers can call whichever object is in scope.
69
+
70
+ ## How detection works
71
+
72
+ `getEnvironment()` resolves in this precedence order:
73
+
74
+ 1. **Testing** — `process.env.OMEGA_TEST_MODE === 'true'` (set by the test runner / emulator). A test run is a test run regardless of any other signal.
75
+ 2. **Production** — `process.env.ENVIRONMENT === 'production'`.
76
+ 3. **Development** — `process.env.ENVIRONMENT === 'development'`, or `FUNCTIONS_EMULATOR` is set, or `TERM_PROGRAM` is `Apple_Terminal` / `vscode` (running locally).
77
+ 4. **Default** — production. @omega.js/backend's deployed *runtime* can legitimately lack a dev signal (a live Cloud Function has no `FUNCTIONS_EMULATOR`), so "no signal" IS the normal production state. (Contrast UJM/BXM, whose deployed artifacts always carry their signal baked in, so they default to **development** — a bare context there is just build tooling. EM defaults to production for the same reason as @omega.js/backend.)
78
+
79
+ ## Adding a new helper
80
+
81
+ If you need a new environment-derived helper, add it next to the others on the Manager in [src/manager/index.js](../src/manager/index.js), and forward it from the route context (ctx) if request handlers need it. Don't read `process.env` ad-hoc elsewhere — derive from `getEnvironment()` so there is one source of truth and no chance of drift.
82
+
83
+ ## Why this matters
84
+
85
+ **One signal, used everywhere.** The test runner sets `OMEGA_TEST_MODE=true`; every piece of code that calls `isTesting()` (framework or consumer) then sees `true` — no need to invent a per-module env var.
86
+
87
+ **Sub-modules check the same signal.** When framework code (an analytics flush, a webhook fan-out) needs to skip side effects in tests, it checks `isTesting()` — the same answer the consumer's own code gets. No drift.
88
+
89
+ **`is*()` can never disagree with `getEnvironment()`.** Because the checks derive from the single resolver instead of reading raw signals, there is exactly one definition of "what environment is this," and a wrong-but-confident gate (leaking real emails during a test run) is structurally impossible.
90
+
91
+ ## See also
92
+
93
+ - [test-framework.md](test-framework.md) — `OMEGA_TEST_MODE` is set automatically by the test runner; `TEST_EXTENDED_MODE` gates real external APIs.
@@ -0,0 +1,10 @@
1
+ # File Naming Conventions
2
+
3
+ | Type | Location | Naming |
4
+ |------|----------|--------|
5
+ | Routes | `routes/{name}/` | `index.js` or `{method}.js` |
6
+ | Schemas | `schemas/{name}/` | `index.js` or `{method}.js` |
7
+ | Auth Events | `events/auth/` | `{event}.js` |
8
+ | Auth Hooks (consumer) | `src/hooks/auth/` | `{event}.js` |
9
+ | Cron Jobs (@omega.js/backend) | `events/cron/daily/` | `{job}.js` |
10
+ | Cron Jobs (consumer) | `src/hooks/cron/daily/` | `{job}.js` |