@vritti/api-sdk 0.4.3 → 0.4.6

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 (67) hide show
  1. package/dist/auth.cjs +58 -17
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +1 -1
  4. package/dist/auth.d.ts +1 -1
  5. package/dist/auth.js +58 -17
  6. package/dist/auth.js.map +1 -1
  7. package/dist/cache.cjs +51 -17
  8. package/dist/cache.cjs.map +1 -1
  9. package/dist/cache.js +51 -17
  10. package/dist/cache.js.map +1 -1
  11. package/dist/catalog-resolver.d.cts +2 -2
  12. package/dist/catalog-resolver.d.ts +2 -2
  13. package/dist/{code-pattern-BP4zkNtT.d.cts → code-pattern-DMlbIwwn.d.cts} +3 -3
  14. package/dist/{code-pattern-BP4zkNtT.d.ts → code-pattern-DMlbIwwn.d.ts} +3 -3
  15. package/dist/data-table.cjs +154 -48
  16. package/dist/data-table.cjs.map +1 -1
  17. package/dist/data-table.js +154 -48
  18. package/dist/data-table.js.map +1 -1
  19. package/dist/database.cjs +101 -31
  20. package/dist/database.cjs.map +1 -1
  21. package/dist/database.js +101 -31
  22. package/dist/database.js.map +1 -1
  23. package/dist/decorators.cjs +1 -1
  24. package/dist/decorators.cjs.map +1 -1
  25. package/dist/decorators.d.cts +2 -2
  26. package/dist/decorators.d.ts +2 -2
  27. package/dist/decorators.js +1 -1
  28. package/dist/decorators.js.map +1 -1
  29. package/dist/drizzle-pg-core.cjs +1 -1
  30. package/dist/drizzle-pg-core.cjs.map +1 -1
  31. package/dist/drizzle-pg-core.d.cts +1 -1
  32. package/dist/drizzle-pg-core.d.ts +1 -1
  33. package/dist/drizzle-pg-core.js +1 -1
  34. package/dist/drizzle-pg-core.js.map +1 -1
  35. package/dist/email.cjs +22 -6
  36. package/dist/email.cjs.map +1 -1
  37. package/dist/email.js +22 -6
  38. package/dist/email.js.map +1 -1
  39. package/dist/files.cjs +1 -1
  40. package/dist/files.cjs.map +1 -1
  41. package/dist/files.d.cts +3 -3
  42. package/dist/files.d.ts +3 -3
  43. package/dist/files.js +1 -1
  44. package/dist/files.js.map +1 -1
  45. package/dist/filters.cjs +18 -4
  46. package/dist/filters.cjs.map +1 -1
  47. package/dist/filters.js +18 -4
  48. package/dist/filters.js.map +1 -1
  49. package/dist/index.d.cts +1 -1
  50. package/dist/index.d.ts +1 -1
  51. package/dist/license.d.cts +1 -1
  52. package/dist/license.d.ts +1 -1
  53. package/dist/logger.cjs +49 -15
  54. package/dist/logger.cjs.map +1 -1
  55. package/dist/logger.js +49 -15
  56. package/dist/logger.js.map +1 -1
  57. package/dist/nats.cjs +51 -19
  58. package/dist/nats.cjs.map +1 -1
  59. package/dist/nats.js +50 -18
  60. package/dist/nats.js.map +1 -1
  61. package/dist/root.cjs +44 -12
  62. package/dist/root.cjs.map +1 -1
  63. package/dist/root.js +44 -12
  64. package/dist/root.js.map +1 -1
  65. package/dist/{types-BQY0Aa1p.d.cts → types-VDKSjje5.d.cts} +1 -1
  66. package/dist/{types-BQY0Aa1p.d.ts → types-VDKSjje5.d.ts} +1 -1
  67. package/package.json +38 -38
@@ -1,7 +1,7 @@
1
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
2
  import { AnyPgColumn } from 'drizzle-orm/pg-core';
3
3
  export * from 'drizzle-orm/pg-core';
4
- import { C as CodeOptions } from './code-pattern-BP4zkNtT.js';
4
+ import { C as CodeOptions } from './code-pattern-DMlbIwwn.js';
5
5
 
6
6
  declare function codeCheck(name: string, column: AnyPgColumn, options?: CodeOptions): drizzle_orm_pg_core.CheckBuilder;
7
7
 
@@ -7,7 +7,7 @@ import { sql } from "drizzle-orm";
7
7
  import { check } from "drizzle-orm/pg-core";
8
8
 
9
9
  // src/decorators/code-pattern.ts
10
- var SEGMENT = "[a-z][a-z0-9-]*";
10
+ var SEGMENT = "[a-z0-9][a-z0-9-]*";
11
11
  var CODE_PATTERN_SOURCE = `^${SEGMENT}$`;
12
12
  var DOTTED_CODE_PATTERN_SOURCE = `^${SEGMENT}(\\.${SEGMENT})*$`;
13
13
  var CODE_PATTERN = new RegExp(CODE_PATTERN_SOURCE);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/drizzle-pg-core.ts","../src/decorators/code-pattern.ts"],"sourcesContent":["// Re-export all of drizzle-orm/pg-core\nexport * from 'drizzle-orm/pg-core';\n\nimport { sql } from 'drizzle-orm';\nimport { type AnyPgColumn, check } from 'drizzle-orm/pg-core';\nimport { CODE_PATTERN_SOURCE, type CodeOptions, DOTTED_CODE_PATTERN_SOURCE } from './decorators/code-pattern';\n\n// Builds a Postgres CHECK constraint enforcing the canonical code format on a column\nexport function codeCheck(name: string, column: AnyPgColumn, options?: CodeOptions) {\n const source = options?.dotted ? DOTTED_CODE_PATTERN_SOURCE : CODE_PATTERN_SOURCE;\n return check(name, sql`${column} ~ ${sql.raw(`'${source}'`)}`);\n}\n","// Canonical entity \"code\" format — the single source of truth for DTOs, DB checks, and frontend forms.\n// A code is a single lowercase word: starts with a letter, then lowercase letters, digits, or hyphens.\n// The dotted variant allows dot-separated segments (e.g. permission codes like \"add.salt\").\n\nconst SEGMENT = '[a-z][a-z0-9-]*';\n\nexport const CODE_PATTERN_SOURCE = `^${SEGMENT}$`;\nexport const DOTTED_CODE_PATTERN_SOURCE = `^${SEGMENT}(\\\\.${SEGMENT})*$`;\n\nexport const CODE_PATTERN = new RegExp(CODE_PATTERN_SOURCE);\nexport const DOTTED_CODE_PATTERN = new RegExp(DOTTED_CODE_PATTERN_SOURCE);\n\nexport interface CodeOptions {\n dotted?: boolean;\n}\n\n// Returns the regex for the requested code variant\nexport function codePattern(options?: CodeOptions): RegExp {\n return options?.dotted ? DOTTED_CODE_PATTERN : CODE_PATTERN;\n}\n\n// Returns the Postgres regex source string for the requested code variant (for CHECK constraints)\nexport function codePatternSource(options?: CodeOptions): string {\n return options?.dotted ? DOTTED_CODE_PATTERN_SOURCE : CODE_PATTERN_SOURCE;\n}\n\n// Human-readable validation message for the requested code variant\nexport function codeMessage(property: string, options?: CodeOptions): string {\n return options?.dotted\n ? `${property} must be lowercase words separated by dots.`\n : `${property} must be lowercase letters, numbers, and hyphens only.`;\n}\n"],"mappings":";;;;AACA,cAAc;AAEd,SAASA,WAAW;AACpB,SAA2BC,aAAa;;;ACAxC,IAAMC,UAAU;AAET,IAAMC,sBAAsB,IAAID,OAAAA;AAChC,IAAME,6BAA6B,IAAIF,OAAAA,OAAcA,OAAAA;AAErD,IAAMG,eAAe,IAAIC,OAAOH,mBAAAA;AAChC,IAAMI,sBAAsB,IAAID,OAAOF,0BAAAA;;;ADFvC,SAASI,UAAUC,MAAcC,QAAqBC,SAAqB;AAChF,QAAMC,SAASD,SAASE,SAASC,6BAA6BC;AAC9D,SAAOC,MAAMP,MAAMQ,MAAMP,MAAAA,MAAYO,IAAIC,IAAI,IAAIN,MAAAA,GAAS,CAAA,EAAG;AAC/D;AAHgBJ;","names":["sql","check","SEGMENT","CODE_PATTERN_SOURCE","DOTTED_CODE_PATTERN_SOURCE","CODE_PATTERN","RegExp","DOTTED_CODE_PATTERN","codeCheck","name","column","options","source","dotted","DOTTED_CODE_PATTERN_SOURCE","CODE_PATTERN_SOURCE","check","sql","raw"]}
1
+ {"version":3,"sources":["../src/drizzle-pg-core.ts","../src/decorators/code-pattern.ts"],"sourcesContent":["// Re-export all of drizzle-orm/pg-core\nexport * from 'drizzle-orm/pg-core';\n\nimport { sql } from 'drizzle-orm';\nimport { type AnyPgColumn, check } from 'drizzle-orm/pg-core';\nimport { CODE_PATTERN_SOURCE, type CodeOptions, DOTTED_CODE_PATTERN_SOURCE } from './decorators/code-pattern';\n\n// Builds a Postgres CHECK constraint enforcing the canonical code format on a column\nexport function codeCheck(name: string, column: AnyPgColumn, options?: CodeOptions) {\n const source = options?.dotted ? DOTTED_CODE_PATTERN_SOURCE : CODE_PATTERN_SOURCE;\n return check(name, sql`${column} ~ ${sql.raw(`'${source}'`)}`);\n}\n","// Canonical entity \"code\" format — the single source of truth for DTOs, DB checks, and frontend forms.\n// A code is a single lowercase word of letters, digits, and hyphens, starting with a letter or digit.\n// Real-world codes lead with a digit often enough (pack sizes \"62g\", sizes \"5xl\") that requiring a\n// leading letter forced callers to mangle them.\n// The dotted variant allows dot-separated segments (e.g. permission codes like \"add.salt\").\n\nconst SEGMENT = '[a-z0-9][a-z0-9-]*';\n\nexport const CODE_PATTERN_SOURCE = `^${SEGMENT}$`;\nexport const DOTTED_CODE_PATTERN_SOURCE = `^${SEGMENT}(\\\\.${SEGMENT})*$`;\n\nexport const CODE_PATTERN = new RegExp(CODE_PATTERN_SOURCE);\nexport const DOTTED_CODE_PATTERN = new RegExp(DOTTED_CODE_PATTERN_SOURCE);\n\nexport interface CodeOptions {\n dotted?: boolean;\n}\n\n// Returns the regex for the requested code variant\nexport function codePattern(options?: CodeOptions): RegExp {\n return options?.dotted ? DOTTED_CODE_PATTERN : CODE_PATTERN;\n}\n\n// Returns the Postgres regex source string for the requested code variant (for CHECK constraints)\nexport function codePatternSource(options?: CodeOptions): string {\n return options?.dotted ? DOTTED_CODE_PATTERN_SOURCE : CODE_PATTERN_SOURCE;\n}\n\n// Human-readable validation message for the requested code variant\nexport function codeMessage(property: string, options?: CodeOptions): string {\n return options?.dotted\n ? `${property} must be lowercase words separated by dots.`\n : `${property} must be lowercase letters, numbers, and hyphens only.`;\n}\n"],"mappings":";;;;AACA,cAAc;AAEd,SAASA,WAAW;AACpB,SAA2BC,aAAa;;;ACExC,IAAMC,UAAU;AAET,IAAMC,sBAAsB,IAAID,OAAAA;AAChC,IAAME,6BAA6B,IAAIF,OAAAA,OAAcA,OAAAA;AAErD,IAAMG,eAAe,IAAIC,OAAOH,mBAAAA;AAChC,IAAMI,sBAAsB,IAAID,OAAOF,0BAAAA;;;ADJvC,SAASI,UAAUC,MAAcC,QAAqBC,SAAqB;AAChF,QAAMC,SAASD,SAASE,SAASC,6BAA6BC;AAC9D,SAAOC,MAAMP,MAAMQ,MAAMP,MAAAA,MAAYO,IAAIC,IAAI,IAAIN,MAAAA,GAAS,CAAA,EAAG;AAC/D;AAHgBJ;","names":["sql","check","SEGMENT","CODE_PATTERN_SOURCE","DOTTED_CODE_PATTERN_SOURCE","CODE_PATTERN","RegExp","DOTTED_CODE_PATTERN","codeCheck","name","column","options","source","dotted","DOTTED_CODE_PATTERN_SOURCE","CODE_PATTERN_SOURCE","check","sql","raw"]}
package/dist/email.cjs CHANGED
@@ -51,13 +51,22 @@ var import_pluralize_esm = __toESM(require("pluralize-esm"), 1);
51
51
  // src/email/email.service.ts
52
52
  function _ts_decorate(decorators, target, key, desc) {
53
53
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
54
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
55
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
54
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
55
+ r = Reflect.decorate(decorators, target, key, desc);
56
+ } else {
57
+ for (var i = decorators.length - 1; i >= 0; i--) {
58
+ if (d = decorators[i]) {
59
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
60
+ }
61
+ }
62
+ }
56
63
  return c > 3 && r && Object.defineProperty(target, key, r), r;
57
64
  }
58
65
  __name(_ts_decorate, "_ts_decorate");
59
- function _ts_metadata(k, v) {
60
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
66
+ function _ts_metadata(metadataKey, metadataValue) {
67
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
68
+ return Reflect.metadata(metadataKey, metadataValue);
69
+ }
61
70
  }
62
71
  __name(_ts_metadata, "_ts_metadata");
63
72
  var EMAIL_SANS = "'IBM Plex Sans',Helvetica,Arial,sans-serif";
@@ -634,8 +643,15 @@ EmailService = _ts_decorate([
634
643
  // src/email/email.module.ts
635
644
  function _ts_decorate2(decorators, target, key, desc) {
636
645
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
637
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
638
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
646
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
647
+ r = Reflect.decorate(decorators, target, key, desc);
648
+ } else {
649
+ for (var i = decorators.length - 1; i >= 0; i--) {
650
+ if (d = decorators[i]) {
651
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
652
+ }
653
+ }
654
+ }
639
655
  return c > 3 && r && Object.defineProperty(target, key, r), r;
640
656
  }
641
657
  __name(_ts_decorate2, "_ts_decorate");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/email/index.ts","../src/email/email.module.ts","../src/email/email.service.ts","../src/pluralize.ts"],"sourcesContent":["// Email module — Brevo-backed transactional email\nexport { EmailModule } from './email.module';\nexport { EmailService } from './email.service';\n","import { Global, Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { EmailService } from './email.service';\n\n@Global()\n@Module({\n imports: [ConfigModule],\n providers: [EmailService],\n exports: [EmailService],\n})\nexport class EmailModule {}\n","import { BrevoClient, BrevoError, BrevoTimeoutError } from '@getbrevo/brevo';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { pluralize } from '../pluralize';\n\n// Email-safe font stacks. Web fonts only land in clients that allow them; the fallbacks carry the rest.\nconst EMAIL_SANS = \"'IBM Plex Sans',Helvetica,Arial,sans-serif\";\nconst EMAIL_MONO = \"'JetBrains Mono','Courier New',Courier,monospace\";\n\n// Brand marks inlined from apps/cloud-web/src/assets/vritti_cloud_{light,dark}.svg, sized for the email\n// header (viewBox 407x67 -> 170x28). Inline SVG renders in Apple Mail and iOS Mail; Gmail, Outlook and\n// Yahoo strip <svg> from email bodies, so configure EMAIL_LOGO_LIGHT_URL with a hosted PNG to reach those.\nconst VRITTI_CLOUD_LOGO_LIGHT = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_56_379)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_56_379)\"/><path d=\"M35.0507 14C37.2595 14.0004 39.0507 15.7911 39.0507 18C39.0507 20.2089 37.2595 21.9996 35.0507 22C34.4331 21.9999 33.8472 21.8594 33.3251 21.6094C33.1714 21.6936 33.0228 21.829 32.8553 22.0293L24.3583 35.0234C24.2882 35.1904 24.2274 35.3388 24.1805 35.4658C24.1331 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8587 24.099 35.9018 24.1229 35.9238C24.1488 35.9469 24.2031 35.9626 24.3085 35.9502C26.1439 35.5539 27.4169 35.1177 28.4637 34.5029C29.5109 33.8878 30.3371 33.0905 31.2772 31.9678L33.3827 28.5957C33.1697 28.1068 33.0507 27.5673 33.0507 27C33.0507 24.791 34.8417 23.0002 37.0507 23C39.2597 23.0002 41.0507 24.791 41.0507 27C41.0507 29.0504 39.5075 30.7394 37.5194 30.9717C37.459 30.9962 37.397 31.0224 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1076 30.9977 37.0789 31 37.0507 31C36.4652 30.9999 35.9092 30.8725 35.4081 30.6465C35.0278 30.6575 34.6938 30.7719 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2336 51.6277 21.1425 51.6842 21.0507 51.6836C20.9809 51.6826 20.9167 51.6494 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5887 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3412 48.8997 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.0079 39.1768 20.0624 38.9248C20.117 38.6719 20.1966 38.3975 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0507 18.6002 31.0507 18C31.0507 15.7911 32.8418 14.0004 35.0507 14ZM36.9999 25C35.8954 25.0001 34.9999 25.8955 34.9999 27C34.9999 28.1045 35.8954 28.9999 36.9999 29C38.1043 28.9999 38.9999 28.1045 38.9999 27C38.9999 25.8955 38.1043 25.0001 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_56_379)\"/><defs><linearGradient id=\"paint0_linear_56_379\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient><linearGradient id=\"paint1_linear_56_379\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_56_379\" x1=\"30.5245\" y1=\"14\" x2=\"30.5245\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient></defs></svg>`;\n\nconst VRITTI_CLOUD_LOGO_DARK = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_92_90)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_92_90)\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M35.0497 14C37.2588 14 39.0497 15.7909 39.0497 18C39.0497 20.2091 37.2588 22 35.0497 22C34.4321 21.9998 33.8463 21.8595 33.3241 21.6094C33.1708 21.6937 33.0224 21.8295 32.8554 22.0293L24.3583 35.0234C24.2882 35.1903 24.2274 35.3389 24.1805 35.4658C24.1332 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8586 24.099 35.9018 24.1229 35.9238C24.1489 35.9468 24.2034 35.9625 24.3085 35.9502C26.1438 35.5539 27.4169 35.1177 28.4638 34.5029C29.5108 33.8878 30.3372 33.0905 31.2772 31.9678L33.3817 28.5967C33.1686 28.1076 33.0497 27.5675 33.0497 27C33.0497 24.7912 34.841 23.0006 37.0497 23C39.2588 23 41.0497 24.7909 41.0497 27C41.0497 29.0499 39.5078 30.7378 37.5204 30.9707C37.4596 30.9954 37.3974 31.0223 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1074 30.9977 37.0781 31 37.0497 31C36.4643 30.9999 35.9082 30.8726 35.4071 30.6465C35.0273 30.6577 34.6935 30.7722 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2337 51.6276 21.1424 51.6841 21.0507 51.6836C20.981 51.6826 20.9167 51.6493 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5888 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3413 48.8996 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.008 39.1768 20.0624 38.9248C20.117 38.672 20.1966 38.3974 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0497 18.6003 31.0497 18C31.0497 15.7912 32.841 14.0006 35.0497 14ZM36.9999 25C35.8955 25.0002 34.9999 25.8956 34.9999 27C34.9999 28.1044 35.8955 28.9998 36.9999 29C38.1042 28.9998 38.9999 28.1044 38.9999 27C38.9999 25.8956 38.1042 25.0002 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_92_90)\"/><defs><linearGradient id=\"paint0_linear_92_90\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient><linearGradient id=\"paint1_linear_92_90\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_92_90\" x1=\"30.5246\" y1=\"14\" x2=\"30.5246\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient></defs></svg>`;\n\ntype EmailNoticeTone = 'warn' | 'danger' | 'success';\ntype EmailButtonTone = 'primary' | 'danger';\n\nconst EMAIL_NOTICE_TONES: Record<\n EmailNoticeTone,\n { background: string; color: string; boxClass: string; textClass: string }\n> = {\n warn: { background: '#fdf6e7', color: '#8a6410', boxClass: 'e-warnbox', textClass: 'e-warn' },\n danger: { background: '#fdeceb', color: '#a32b21', boxClass: 'e-dangerbox', textClass: 'e-danger' },\n success: { background: '#eaf6ee', color: '#1c6b3f', boxClass: 'e-successbox', textClass: 'e-success' },\n};\n\nconst EMAIL_BUTTON_TONES: Record<EmailButtonTone, string> = {\n primary: '#1b74d1',\n danger: '#c0392b',\n};\n\n@Injectable()\nexport class EmailService {\n private readonly logger = new Logger(EmailService.name);\n private readonly brevoClient: BrevoClient;\n private readonly senderEmail: string;\n private readonly senderName: string;\n private readonly logoLightUrl?: string;\n private readonly logoDarkUrl?: string;\n private readonly frontendBaseUrl?: string;\n\n constructor(private readonly configService: ConfigService) {\n const apiKey = this.configService.get<string>('BREVO_API_KEY');\n\n if (!apiKey) {\n this.logger.error('BREVO_API_KEY is not configured. Email sending will fail.');\n throw new Error('Email service configuration error: Missing BREVO_API_KEY');\n }\n\n // Initialize Brevo client with built-in retry support\n this.brevoClient = new BrevoClient({ apiKey, maxRetries: 3 });\n\n // Get sender configuration\n const senderEmail = this.configService.get<string>('SENDER_EMAIL');\n const senderName = this.configService.get<string>('SENDER_NAME');\n\n if (!senderEmail || !senderName) {\n this.logger.error('Sender email or name is not configured.');\n throw new Error('Email service configuration error: Missing SENDER_EMAIL or SENDER_NAME');\n }\n\n this.senderEmail = senderEmail;\n this.senderName = senderName;\n\n // Hosted brand marks for email headers. Must be PNG - most clients block SVG.\n this.logoLightUrl = this.configService.get<string>('EMAIL_LOGO_LIGHT_URL');\n this.logoDarkUrl = this.configService.get<string>('EMAIL_LOGO_DARK_URL') || this.logoLightUrl;\n\n if (!this.logoLightUrl) {\n this.logger.warn(\n 'EMAIL_LOGO_LIGHT_URL is not configured. Emails fall back to an inline SVG wordmark, which Gmail, Outlook and Yahoo strip.',\n );\n }\n\n // Base URL for links back into the web app. Optional at boot so consumers that send no\n // link-bearing emails still start; the senders that need it throw with a clear message.\n this.frontendBaseUrl = this.configService.get<string>('FRONTEND_BASE_URL')?.replace(/\\/+$/, '');\n\n if (!this.frontendBaseUrl) {\n this.logger.warn('FRONTEND_BASE_URL is not configured. Emails containing web app links will fail to send.');\n }\n\n this.logger.log('Brevo email service initialized successfully');\n }\n\n // Sends an email verification OTP to the given recipient\n async sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your verification code is ${otp}. It expires in ${expiry}.`,\n heading: 'Verify Your Email',\n body: [\n this.emailText(`Hello ${name} &mdash; enter the code below to finish setting up your Vritti AI Cloud account.`),\n this.emailCode('Verification Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'Enter the code below to finish setting up your Vritti AI Cloud account.',\n `Verification Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Verify Your Email - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Verification email sent to ${email}`);\n }\n\n // Sends a password reset OTP to the given recipient\n async sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your password reset code is ${otp}. It expires in ${expiry}.`,\n heading: 'Reset Your Password',\n body: [\n this.emailText(\n `Hello ${name} &mdash; we received a request to reset your password. Enter the code below to choose a new one.`,\n ),\n this.emailCode('Reset Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailNotice(\n '<strong>Never share this code.</strong> Vritti will never ask you for it by email, phone or chat.',\n 'danger',\n ),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request a reset? You can ignore this email &mdash; your password stays unchanged and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'We received a request to reset your password. Enter the code below to choose a new one.',\n `Reset Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n 'Never share this code. Vritti will never ask you for it by email, phone or chat.',\n \"Didn't request a reset? You can ignore this email - your password stays unchanged and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Reset Your Password - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Password reset email sent to ${email}`);\n }\n\n // Sends an email change notification to the old address with a revert link\n async sendEmailChangeNotification(\n oldEmail: string,\n newEmail: string,\n revertToken: string,\n revertExpiresAt: Date,\n displayName?: string,\n ): Promise<void> {\n const name = displayName || 'there';\n const hoursUntilExpiry = Math.floor((revertExpiresAt.getTime() - Date.now()) / 3_600_000);\n const window = `${hoursUntilExpiry} ${pluralize('hour', hoursUntilExpiry)}`;\n\n const revertLink = `${this.requireFrontendBaseUrl()}/settings/profile/revert-email?token=${revertToken}`;\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is now ${newEmail}.`,\n heading: 'Your Email Address Changed',\n body: [\n this.emailText(`Hello ${name} &mdash; the email address on your Vritti AI Cloud account has been changed.`),\n this.emailFields([\n { label: 'Previous Email', value: oldEmail },\n { label: 'New Email', value: newEmail },\n ]),\n this.emailNotice(\n `<strong>Didn't make this change?</strong> You can revert it within the next ${window}.`,\n 'danger',\n ),\n this.emailButton(revertLink, 'Revert Email Change', 'danger'),\n this.emailDivider(),\n this.emailMuted('If you made this change yourself, no action is needed &mdash; you can ignore this email.'),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The email address on your Vritti AI Cloud account has been changed.',\n `Previous Email: ${oldEmail}`,\n `New Email: ${newEmail}`,\n `Didn't make this change? You can revert it within the next ${window}:`,\n revertLink,\n 'If you made this change yourself, no action is needed - you can ignore this email.',\n ]);\n\n await this.sendEmail({\n to: [{ email: oldEmail, name }],\n subject: 'Your Email Address Has Been Changed - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email change notification sent to ${oldEmail}`);\n }\n\n // Sends a confirmation to the restored email address after a revert\n async sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is back to ${email}.`,\n heading: 'Email Change Reverted',\n body: [\n this.emailText(\n `Hello ${name} &mdash; the recent change to your email address has been reverted. Your account email is now:`,\n ),\n this.emailNotice(email, 'success', true),\n this.emailDivider(),\n this.emailMuted(\"Didn't request this revert? Contact our support team immediately.\"),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The recent change to your email address has been reverted. Your account email is now:',\n email,\n \"Didn't request this revert? Contact our support team immediately.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Email Address Change Reverted - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email revert confirmation sent to ${email}`);\n }\n\n // Sends an invite email to a new portal user with their set-password link\n async sendInviteEmail(params: { to: string; name: string; inviteUrl: string }): Promise<void> {\n const { to, name, inviteUrl } = params;\n\n const htmlContent = this.renderEmailShell({\n preheader: 'Set your password to activate your Vritti AI Cloud account.',\n heading: \"You're Invited\",\n body: [\n this.emailText(\n `Hello ${name} &mdash; you've been invited to join Vritti AI Cloud. Set your password to activate your account.`,\n ),\n this.emailButton(inviteUrl, 'Set Your Password'),\n this.emailNotice('This invite link is single-use and expires once your password is set.', 'warn'),\n this.emailDivider(),\n this.emailMuted(\n `If the button doesn't work, copy this link into your browser:<br><span style=\"word-break:break-all;\">${inviteUrl}</span>`,\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n \"You've been invited to join Vritti AI Cloud. Visit the link below to set your password and activate your account:\",\n inviteUrl,\n 'This invite link is single-use and expires once your password is set.',\n ]);\n\n await this.sendEmail({\n to: [{ email: to, name }],\n subject: 'You have been invited to Vritti AI',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Invite email sent to ${to}`);\n }\n\n // Sends a transactional email with custom subject, HTML, and text content\n async sendTransactionalEmail(params: {\n to: { email: string; name?: string };\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n await this.sendEmail({\n to: [params.to],\n subject: params.subject,\n htmlContent: params.htmlContent,\n textContent: params.textContent,\n });\n this.logger.log(`Transactional email sent to ${params.to.email}`);\n }\n\n // Verifies Brevo API connectivity — a 400 response means the API is reachable\n async verifyConnection(): Promise<boolean> {\n try {\n await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: [{ email: this.senderEmail }],\n subject: 'Connection Test',\n htmlContent: '<p>Test</p>',\n });\n return true;\n } catch (err) {\n // A 400 error means the API is reachable but params are incomplete — still a successful connection test\n if (err instanceof BrevoError && err.statusCode === 400) {\n return true;\n }\n this.logger.error('Brevo connection verification failed:', err);\n return false;\n }\n }\n\n // Sends a transactional email via Brevo — retries handled internally by BrevoClient\n // Renders the shared transactional email shell: brand header, card, heading, body rows, footer\n private renderEmailShell(params: { preheader: string; heading: string; body: string }): string {\n const { preheader, heading, body } = params;\n\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"x-apple-disable-message-reformatting\">\n<meta name=\"color-scheme\" content=\"light dark\">\n<meta name=\"supported-color-schemes\" content=\"light dark\">\n<title>${heading}</title>\n<!--[if mso]>\n<style>body,table,td,a{font-family:Arial,Helvetica,sans-serif !important;}</style>\n<![endif]-->\n<style>\n @media only screen and (max-width:620px){\n .m-pad{padding-left:24px !important;padding-right:24px !important;}\n .m-code{font-size:32px !important;letter-spacing:8px !important;}\n .m-h1{font-size:26px !important;line-height:32px !important;}\n }\n @media (prefers-color-scheme:dark){\n .e-bg{background-color:#0f1214 !important;}\n .e-card{background-color:#1c2126 !important;border-color:#2b3238 !important;}\n .e-h1{color:#f0f3f5 !important;}\n .e-body{color:#a3adb8 !important;}\n .e-panel{background-color:#14181c !important;border-color:#2b3238 !important;}\n .e-label{color:#8b96a2 !important;}\n .e-code{color:#63a8ee !important;}\n .e-value{color:#f0f3f5 !important;}\n .e-warnbox{background-color:#2a2317 !important;}\n .e-warn{color:#e0b45e !important;}\n .e-dangerbox{background-color:#2b1a18 !important;}\n .e-danger{color:#ef8a7f !important;}\n .e-successbox{background-color:#16261c !important;}\n .e-success{color:#5fc98a !important;}\n .e-rule{background-color:#2b3238 !important;}\n .e-muted{color:#8b96a2 !important;}\n .e-footer{color:#79838e !important;}\n .e-logo-light{display:none !important;}\n .e-logo-dark{display:block !important;}\n }\n</style>\n</head>\n<body class=\"e-bg\" style=\"margin:0;padding:0;background-color:#f7f9fb;\">\n<span style=\"display:none!important;visibility:hidden;opacity:0;color:transparent;height:0;width:0;overflow:hidden;mso-hide:all;\">${preheader}</span>\n\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-bg\" style=\"background-color:#f7f9fb;\">\n <tr>\n <td align=\"center\" style=\"padding:40px 12px 56px 12px;\">\n\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"600\" style=\"width:600px;max-width:600px;\">\n\n <tr>\n <td style=\"padding:0 0 20px 2px;\">\n ${this.renderEmailBrand()}\n </td>\n </tr>\n\n <tr>\n <td class=\"e-card\" style=\"background-color:#ffffff;border:1px solid #e6ebf2;border-radius:12px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n\n <tr>\n <td class=\"m-pad m-h1 e-h1\" style=\"padding:40px 40px 0 40px;font-family:${EMAIL_SANS};font-size:28px;line-height:34px;mso-line-height-rule:exactly;color:#1b2434;font-weight:600;letter-spacing:-0.4px;\">\n ${heading}\n </td>\n </tr>\n${body}\n <tr><td style=\"height:36px;line-height:36px;font-size:0;\">&nbsp;</td></tr>\n\n </table>\n </td>\n </tr>\n\n <tr>\n <td class=\"m-pad e-footer\" style=\"padding:22px 2px 0 2px;font-family:${EMAIL_SANS};font-size:12px;line-height:20px;mso-line-height-rule:exactly;color:#8a95a5;letter-spacing:0.01em;\">\n Vritti AI Cloud<br>\n Automated message &mdash; replies aren't monitored.\n </td>\n </tr>\n\n </table>\n\n </td>\n </tr>\n</table>\n</body>\n</html>\n `.trim();\n }\n\n // Builds the plain-text alternative from the same blocks the HTML shell renders\n private renderTextShell(blocks: string[]): string {\n const footer = ['---', 'Vritti AI Cloud', \"Automated message - replies aren't monitored.\"].join('\\n');\n return [...blocks, footer].join('\\n\\n');\n }\n\n // Body paragraph\n private emailText(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-body\" style=\"padding:14px 40px 0 40px;font-family:${EMAIL_SANS};font-size:15px;line-height:25px;mso-line-height-rule:exactly;color:#5d6b80;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Monospace OTP tile with an uppercase label\n private emailCode(label: string, code: string): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">\n <tr>\n <td align=\"center\" class=\"e-label\" style=\"padding:18px 16px 6px 16px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${label}\n </td>\n </tr>\n <tr>\n <td align=\"center\" class=\"m-code e-code\" style=\"padding:0 16px 20px 16px;font-family:${EMAIL_MONO};font-size:38px;line-height:46px;mso-line-height-rule:exactly;letter-spacing:11px;color:#1b74d1;font-weight:600;text-indent:11px;\">\n ${code}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Tinted callout. `center` renders the content as a centred mono value instead of a sentence\n private emailNotice(html: string, tone: EmailNoticeTone, center = false): string {\n const palette = EMAIL_NOTICE_TONES[tone];\n const typography = center\n ? `font-family:${EMAIL_MONO};font-size:17px;line-height:26px;font-weight:600;`\n : `font-family:${EMAIL_SANS};font-size:13px;line-height:19px;letter-spacing:0.01em;`;\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:16px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"${palette.boxClass}\" style=\"background-color:${palette.background};border-radius:8px;\">\n <tr>\n <td align=\"${center ? 'center' : 'left'}\" class=\"${palette.textClass}\" style=\"padding:${center ? '16px 14px' : '11px 14px'};${typography}mso-line-height-rule:exactly;color:${palette.color};\">\n ${html}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Label/value list rendered with monospace values\n private emailFields(fields: { label: string; value: string }[]): string {\n const rows = fields\n .map(\n (field, index) => `\n <tr>\n <td class=\"e-label\" style=\"padding:${index === 0 ? '18px' : '14px'} 18px 0 18px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${field.label}\n </td>\n </tr>\n <tr>\n <td class=\"e-value\" style=\"padding:4px 18px ${index === fields.length - 1 ? '18px' : '0'} 18px;font-family:${EMAIL_MONO};font-size:15px;line-height:23px;mso-line-height-rule:exactly;color:#1b2434;word-break:break-all;\">\n ${field.value}\n </td>\n </tr>`,\n )\n .join('');\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">${rows}\n </table>\n </td>\n </tr>`;\n }\n\n // Solid CTA button. Colours are theme-independent so the fill reads the same in light and dark\n private emailButton(href: string, label: string, tone: EmailButtonTone = 'primary'): string {\n const background = EMAIL_BUTTON_TONES[tone];\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:24px 40px 0 40px;\">\n <!--[if mso]>\n <v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:240px;\" arcsize=\"20%\" stroke=\"f\" fillcolor=\"${background}\">\n <w:anchorlock/>\n <center style=\"color:#ffffff;font-family:Arial,sans-serif;font-size:15px;font-weight:600;\">${label}</center>\n </v:roundrect>\n <![endif]-->\n <!--[if !mso]><!-->\n <a href=\"${href}\" style=\"display:inline-block;padding:13px 28px;background-color:${background};color:#ffffff;text-decoration:none;border-radius:8px;font-family:${EMAIL_SANS};font-size:15px;line-height:18px;font-weight:600;letter-spacing:0.01em;\">${label}</a>\n <!--<![endif]-->\n </td>\n </tr>`;\n }\n\n // Hairline rule separating the body from the closing note\n private emailDivider(): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n <tr><td height=\"1\" class=\"e-rule\" style=\"height:1px;line-height:1px;font-size:0;background-color:#eef2f7;\">&nbsp;</td></tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Small closing note below the divider\n private emailMuted(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-muted\" style=\"padding:18px 40px 0 40px;font-family:${EMAIL_SANS};font-size:13px;line-height:21px;mso-line-height-rule:exactly;color:#7b8798;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Renders the email header brand mark: a hosted logo when configured, inline SVG otherwise\n private renderEmailBrand(): string {\n const imgStyle = 'border:0;outline:none;text-decoration:none;width:170px;height:auto;';\n\n // A hosted PNG is preferred: it is the only form Gmail, Outlook and Yahoo will render\n if (this.logoLightUrl) {\n return `<img src=\"${this.logoLightUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-light\" style=\"display:block;${imgStyle}\">\n <img src=\"${this.logoDarkUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-dark\" style=\"display:none;${imgStyle}\">`;\n }\n\n // Both marks ship; the dark one is revealed by the prefers-color-scheme rule\n return `<div class=\"e-logo-light\" style=\"display:block;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_LIGHT}</div>\n <div class=\"e-logo-dark\" style=\"display:none;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_DARK}</div>`;\n }\n\n // Frontend base URL for emails that link back into the web app\n private requireFrontendBaseUrl(): string {\n if (!this.frontendBaseUrl) {\n throw new Error('Email service configuration error: Missing FRONTEND_BASE_URL');\n }\n return this.frontendBaseUrl;\n }\n\n // Renders an OTP expiry window as a pluralised minute count\n private formatExpiry(expiresAt: Date): string {\n const minutes = Math.ceil((expiresAt.getTime() - Date.now()) / 60_000);\n return `${minutes} ${pluralize('minute', minutes)}`;\n }\n private async sendEmail(emailData: {\n to: Array<{ email: string; name?: string }>;\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n try {\n const result = await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: emailData.to,\n subject: emailData.subject,\n htmlContent: emailData.htmlContent,\n textContent: emailData.textContent,\n });\n this.logger.debug(`Email sent successfully. Message ID: ${result.messageId}`);\n } catch (err) {\n if (err instanceof BrevoTimeoutError) {\n this.logger.error('Brevo request timed out after retries.');\n throw new Error('Email sending failed: timeout');\n }\n if (err instanceof BrevoError) {\n if (err.statusCode === 429) {\n this.logger.error('Brevo rate limit exceeded after retries.');\n throw new Error('Email sending failed: rate limit exceeded');\n }\n if (err.statusCode === 401) {\n this.logger.error('Brevo authentication failed. Check your API key.');\n throw new Error('Email service authentication failed');\n }\n if (err.statusCode === 400) {\n this.logger.error('Bad request to Brevo API:', err.message);\n throw new Error(`Invalid email parameters: ${err.message}`);\n }\n this.logger.error(`Brevo API error ${err.statusCode}:`, err.message);\n throw new Error(`Email sending failed: ${err.message}`);\n }\n throw err;\n }\n }\n}\n","// Re-export pluralize-esm's default export as the subpath entry point\nexport { default as pluralize } from 'pluralize-esm';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;ACAA,IAAAA,iBAA+B;AAC/B,IAAAC,iBAA6B;;;ACD7B,mBAA2D;AAC3D,oBAAmC;AACnC,oBAA8B;;;ACD9B,2BAAqC;;;;;;;;;;;;;;ADKrC,IAAMC,aAAa;AACnB,IAAMC,aAAa;AAKnB,IAAMC,0BAA0B;AAEhC,IAAMC,yBAAyB;AAK/B,IAAMC,qBAGF;EACFC,MAAM;IAAEC,YAAY;IAAWC,OAAO;IAAWC,UAAU;IAAaC,WAAW;EAAS;EAC5FC,QAAQ;IAAEJ,YAAY;IAAWC,OAAO;IAAWC,UAAU;IAAeC,WAAW;EAAW;EAClGE,SAAS;IAAEL,YAAY;IAAWC,OAAO;IAAWC,UAAU;IAAgBC,WAAW;EAAY;AACvG;AAEA,IAAMG,qBAAsD;EAC1DC,SAAS;EACTH,QAAQ;AACV;AAGO,IAAMI,eAAN,MAAMA,cAAAA;SAAAA;;;;EACMC,SAAS,IAAIC,qBAAOF,cAAaG,IAAI;EACrCC;EACAC;EACAC;EACAC;EACAC;EACAC;EAEjB,YAA6BC,eAA8B;SAA9BA,gBAAAA;AAC3B,UAAMC,SAAS,KAAKD,cAAcE,IAAY,eAAA;AAE9C,QAAI,CAACD,QAAQ;AACX,WAAKV,OAAOY,MAAM,2DAAA;AAClB,YAAM,IAAIC,MAAM,0DAAA;IAClB;AAGA,SAAKV,cAAc,IAAIW,yBAAY;MAAEJ;MAAQK,YAAY;IAAE,CAAA;AAG3D,UAAMX,cAAc,KAAKK,cAAcE,IAAY,cAAA;AACnD,UAAMN,aAAa,KAAKI,cAAcE,IAAY,aAAA;AAElD,QAAI,CAACP,eAAe,CAACC,YAAY;AAC/B,WAAKL,OAAOY,MAAM,yCAAA;AAClB,YAAM,IAAIC,MAAM,wEAAA;IAClB;AAEA,SAAKT,cAAcA;AACnB,SAAKC,aAAaA;AAGlB,SAAKC,eAAe,KAAKG,cAAcE,IAAY,sBAAA;AACnD,SAAKJ,cAAc,KAAKE,cAAcE,IAAY,qBAAA,KAA0B,KAAKL;AAEjF,QAAI,CAAC,KAAKA,cAAc;AACtB,WAAKN,OAAOV,KACV,2HAAA;IAEJ;AAIA,SAAKkB,kBAAkB,KAAKC,cAAcE,IAAY,mBAAA,GAAsBK,QAAQ,QAAQ,EAAA;AAE5F,QAAI,CAAC,KAAKR,iBAAiB;AACzB,WAAKR,OAAOV,KAAK,yFAAA;IACnB;AAEA,SAAKU,OAAOiB,IAAI,8CAAA;EAClB;;EAGA,MAAMC,sBAAsBC,OAAeC,KAAaC,WAAiBC,aAAqC;AAC5G,UAAMpB,OAAOoB,eAAe;AAC5B,UAAMC,SAAS,KAAKC,aAAaH,SAAAA;AAEjC,UAAMI,cAAc,KAAKC,iBAAiB;MACxCC,WAAW,6BAA6BP,GAAAA,mBAAsBG,MAAAA;MAC9DK,SAAS;MACTC,MAAM;QACJ,KAAKC,UAAU,SAAS5B,IAAAA,kFAAsF;QAC9G,KAAK6B,UAAU,qBAAqBX,GAAAA;QACpC,KAAKY,YAAY,qBAAqBT,MAAAA,uBAA6B,MAAA;QACnE,KAAKU,aAAY;QACjB,KAAKC,WACH,6GAAA;QAEFC,KAAK,EAAA;IACT,CAAA;AAEA,UAAMC,cAAc,KAAKC,gBAAgB;MACvC,SAASnC,IAAAA;MACT;MACA,sBAAsBkB,GAAAA;MACtB,qBAAqBG,MAAAA;MACrB;KACD;AAED,UAAM,KAAKe,UAAU;MACnBC,IAAI;QAAC;UAAEpB;UAAOjB;QAAK;;MACnBsC,SAAS;MACTf;MACAW;IACF,CAAA;AAEA,SAAKpC,OAAOiB,IAAI,8BAA8BE,KAAAA,EAAO;EACvD;;EAGA,MAAMsB,uBAAuBtB,OAAeC,KAAaC,WAAiBC,aAAqC;AAC7G,UAAMpB,OAAOoB,eAAe;AAC5B,UAAMC,SAAS,KAAKC,aAAaH,SAAAA;AAEjC,UAAMI,cAAc,KAAKC,iBAAiB;MACxCC,WAAW,+BAA+BP,GAAAA,mBAAsBG,MAAAA;MAChEK,SAAS;MACTC,MAAM;QACJ,KAAKC,UACH,SAAS5B,IAAAA,kGAAsG;QAEjH,KAAK6B,UAAU,cAAcX,GAAAA;QAC7B,KAAKY,YAAY,qBAAqBT,MAAAA,uBAA6B,MAAA;QACnE,KAAKS,YACH,qGACA,QAAA;QAEF,KAAKC,aAAY;QACjB,KAAKC,WACH,8HAAA;QAEFC,KAAK,EAAA;IACT,CAAA;AAEA,UAAMC,cAAc,KAAKC,gBAAgB;MACvC,SAASnC,IAAAA;MACT;MACA,eAAekB,GAAAA;MACf,qBAAqBG,MAAAA;MACrB;MACA;KACD;AAED,UAAM,KAAKe,UAAU;MACnBC,IAAI;QAAC;UAAEpB;UAAOjB;QAAK;;MACnBsC,SAAS;MACTf;MACAW;IACF,CAAA;AAEA,SAAKpC,OAAOiB,IAAI,gCAAgCE,KAAAA,EAAO;EACzD;;EAGA,MAAMuB,4BACJC,UACAC,UACAC,aACAC,iBACAxB,aACe;AACf,UAAMpB,OAAOoB,eAAe;AAC5B,UAAMyB,mBAAmBC,KAAKC,OAAOH,gBAAgBI,QAAO,IAAKC,KAAKC,IAAG,KAAM,IAAA;AAC/E,UAAMC,SAAS,GAAGN,gBAAAA,QAAoBO,8BAAU,QAAQP,gBAAAA,CAAAA;AAExD,UAAMQ,aAAa,GAAG,KAAKC,uBAAsB,CAAA,wCAA0CX,WAAAA;AAE3F,UAAMpB,cAAc,KAAKC,iBAAiB;MACxCC,WAAW,6CAA6CiB,QAAAA;MACxDhB,SAAS;MACTC,MAAM;QACJ,KAAKC,UAAU,SAAS5B,IAAAA,8EAAkF;QAC1G,KAAKuD,YAAY;UACf;YAAEC,OAAO;YAAkBC,OAAOhB;UAAS;UAC3C;YAAEe,OAAO;YAAaC,OAAOf;UAAS;SACvC;QACD,KAAKZ,YACH,+EAA+EqB,MAAAA,KAC/E,QAAA;QAEF,KAAKO,YAAYL,YAAY,uBAAuB,QAAA;QACpD,KAAKtB,aAAY;QACjB,KAAKC,WAAW,0FAAA;QAChBC,KAAK,EAAA;IACT,CAAA;AAEA,UAAMC,cAAc,KAAKC,gBAAgB;MACvC,SAASnC,IAAAA;MACT;MACA,mBAAmByC,QAAAA;MACnB,cAAcC,QAAAA;MACd,8DAA8DS,MAAAA;MAC9DE;MACA;KACD;AAED,UAAM,KAAKjB,UAAU;MACnBC,IAAI;QAAC;UAAEpB,OAAOwB;UAAUzC;QAAK;;MAC7BsC,SAAS;MACTf;MACAW;IACF,CAAA;AAEA,SAAKpC,OAAOiB,IAAI,qCAAqC0B,QAAAA,EAAU;EACjE;;EAGA,MAAMkB,4BAA4B1C,OAAeG,aAAqC;AACpF,UAAMpB,OAAOoB,eAAe;AAE5B,UAAMG,cAAc,KAAKC,iBAAiB;MACxCC,WAAW,iDAAiDR,KAAAA;MAC5DS,SAAS;MACTC,MAAM;QACJ,KAAKC,UACH,SAAS5B,IAAAA,gGAAoG;QAE/G,KAAK8B,YAAYb,OAAO,WAAW,IAAA;QACnC,KAAKc,aAAY;QACjB,KAAKC,WAAW,mEAAA;QAChBC,KAAK,EAAA;IACT,CAAA;AAEA,UAAMC,cAAc,KAAKC,gBAAgB;MACvC,SAASnC,IAAAA;MACT;MACAiB;MACA;KACD;AAED,UAAM,KAAKmB,UAAU;MACnBC,IAAI;QAAC;UAAEpB;UAAOjB;QAAK;;MACnBsC,SAAS;MACTf;MACAW;IACF,CAAA;AAEA,SAAKpC,OAAOiB,IAAI,qCAAqCE,KAAAA,EAAO;EAC9D;;EAGA,MAAM2C,gBAAgBC,QAAwE;AAC5F,UAAM,EAAExB,IAAIrC,MAAM8D,UAAS,IAAKD;AAEhC,UAAMtC,cAAc,KAAKC,iBAAiB;MACxCC,WAAW;MACXC,SAAS;MACTC,MAAM;QACJ,KAAKC,UACH,SAAS5B,IAAAA,mGAAuG;QAElH,KAAK0D,YAAYI,WAAW,mBAAA;QAC5B,KAAKhC,YAAY,yEAAyE,MAAA;QAC1F,KAAKC,aAAY;QACjB,KAAKC,WACH,wGAAwG8B,SAAAA,SAAkB;QAE5H7B,KAAK,EAAA;IACT,CAAA;AAEA,UAAMC,cAAc,KAAKC,gBAAgB;MACvC,SAASnC,IAAAA;MACT;MACA8D;MACA;KACD;AAED,UAAM,KAAK1B,UAAU;MACnBC,IAAI;QAAC;UAAEpB,OAAOoB;UAAIrC;QAAK;;MACvBsC,SAAS;MACTf;MACAW;IACF,CAAA;AAEA,SAAKpC,OAAOiB,IAAI,wBAAwBsB,EAAAA,EAAI;EAC9C;;EAGA,MAAM0B,uBAAuBF,QAKX;AAChB,UAAM,KAAKzB,UAAU;MACnBC,IAAI;QAACwB,OAAOxB;;MACZC,SAASuB,OAAOvB;MAChBf,aAAasC,OAAOtC;MACpBW,aAAa2B,OAAO3B;IACtB,CAAA;AACA,SAAKpC,OAAOiB,IAAI,+BAA+B8C,OAAOxB,GAAGpB,KAAK,EAAE;EAClE;;EAGA,MAAM+C,mBAAqC;AACzC,QAAI;AACF,YAAM,KAAK/D,YAAYgE,oBAAoBC,iBAAiB;QAC1DC,QAAQ;UAAElD,OAAO,KAAKf;UAAaF,MAAM,KAAKG;QAAW;QACzDkC,IAAI;UAAC;YAAEpB,OAAO,KAAKf;UAAY;;QAC/BoC,SAAS;QACTf,aAAa;MACf,CAAA;AACA,aAAO;IACT,SAAS6C,KAAK;AAEZ,UAAIA,eAAeC,2BAAcD,IAAIE,eAAe,KAAK;AACvD,eAAO;MACT;AACA,WAAKxE,OAAOY,MAAM,yCAAyC0D,GAAAA;AAC3D,aAAO;IACT;EACF;;;EAIQ5C,iBAAiBqC,QAAsE;AAC7F,UAAM,EAAEpC,WAAWC,SAASC,KAAI,IAAKkC;AAErC,WAAO;;;;;;;;;SASFnC,OAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oIAkC2HD,SAAAA;;;;;;;;;;cAUtH,KAAK8C,iBAAgB,CAAA;;;;;;;;;0FASuDxF,UAAAA;oBACtE2C,OAAAA;;;EAGlBC,IAAAA;;;;;;;;iFAQ+E5C,UAAAA;;;;;;;;;;;;;MAa3EyF,KAAI;EACR;;EAGQrC,gBAAgBsC,QAA0B;AAChD,UAAMC,SAAS;MAAC;MAAO;MAAmB;MAAiDzC,KAAK,IAAA;AAChG,WAAO;SAAIwC;MAAQC;MAAQzC,KAAK,MAAA;EAClC;;EAGQL,UAAU+C,MAAsB;AACtC,WAAO;;uFAE4E5F,UAAAA;oBACnE4F,IAAAA;;;EAGlB;;EAGQ9C,UAAU2B,OAAeoB,MAAsB;AACrD,WAAO;;;;;yGAK8F7F,UAAAA;0BAC/EyE,KAAAA;;;;6GAImFxE,UAAAA;0BACnF4F,IAAAA;;;;;;EAMxB;;EAGQ9C,YAAY6C,MAAcE,MAAuBC,SAAS,OAAe;AAC/E,UAAMC,UAAU5F,mBAAmB0F,IAAAA;AACnC,UAAMG,aAAaF,SACf,eAAe9F,UAAAA,sDACf,eAAeD,UAAAA;AAEnB,WAAO;;;8GAGmGgG,QAAQxF,QAAQ,6BAA6BwF,QAAQ1F,UAAU;;mCAE1IyF,SAAS,WAAW,MAAA,YAAkBC,QAAQvF,SAAS,oBAAoBsF,SAAS,cAAc,WAAA,IAAeE,UAAAA,sCAAgDD,QAAQzF,KAAK;0BACvLqF,IAAAA;;;;;;EAMxB;;EAGQpB,YAAY0B,QAAoD;AACtE,UAAMC,OAAOD,OACVE,IACC,CAACC,OAAOC,UAAU;;2DAEiCA,UAAU,IAAI,SAAS,MAAA,4BAAkCtG,UAAAA;0BAC1FqG,MAAM5B,KAAK;;;;oEAI+B6B,UAAUJ,OAAOK,SAAS,IAAI,SAAS,GAAA,qBAAwBtG,UAAAA;0BACzGoG,MAAM3B,KAAK;;0BAEX,EAEnBxB,KAAK,EAAA;AAER,WAAO;;;qMAG0LiD,IAAAA;;;;EAInM;;EAGQxB,YAAY6B,MAAc/B,OAAeqB,OAAwB,WAAmB;AAC1F,UAAMxF,aAAaM,mBAAmBkF,IAAAA;AAEtC,WAAO;;;;+HAIoHU,IAAAA,+FAAmGlG,UAAAA;;iHAEjHmE,KAAAA;;;;6BAIpF+B,IAAAA,oEAAwElG,UAAAA,qEAA+EN,UAAAA,4EAAsFyE,KAAAA;;;;EAIxQ;;EAGQzB,eAAuB;AAC7B,WAAO;;;;;;;;EAQT;;EAGQC,WAAW2C,MAAsB;AACvC,WAAO;;wFAE6E5F,UAAAA;oBACpE4F,IAAAA;;;EAGlB;;EAGQJ,mBAA2B;AACjC,UAAMiB,WAAW;AAGjB,QAAI,KAAKpF,cAAc;AACrB,aAAO,aAAa,KAAKA,YAAY,6FAA6FoF,QAAAA;wBAChH,KAAKnF,WAAW,2FAA2FmF,QAAAA;IAC/H;AAGA,WAAO,8EAA8EvG,uBAAAA;uFACFC,sBAAAA;EACrF;;EAGQoE,yBAAiC;AACvC,QAAI,CAAC,KAAKhD,iBAAiB;AACzB,YAAM,IAAIK,MAAM,8DAAA;IAClB;AACA,WAAO,KAAKL;EACd;;EAGQgB,aAAaH,WAAyB;AAC5C,UAAMsE,UAAU3C,KAAK4C,MAAMvE,UAAU6B,QAAO,IAAKC,KAAKC,IAAG,KAAM,GAAA;AAC/D,WAAO,GAAGuC,OAAAA,QAAWrC,8BAAU,UAAUqC,OAAAA,CAAAA;EAC3C;EACA,MAAcrD,UAAUuD,WAKN;AAChB,QAAI;AACF,YAAMC,SAAS,MAAM,KAAK3F,YAAYgE,oBAAoBC,iBAAiB;QACzEC,QAAQ;UAAElD,OAAO,KAAKf;UAAaF,MAAM,KAAKG;QAAW;QACzDkC,IAAIsD,UAAUtD;QACdC,SAASqD,UAAUrD;QACnBf,aAAaoE,UAAUpE;QACvBW,aAAayD,UAAUzD;MACzB,CAAA;AACA,WAAKpC,OAAO+F,MAAM,wCAAwCD,OAAOE,SAAS,EAAE;IAC9E,SAAS1B,KAAK;AACZ,UAAIA,eAAe2B,gCAAmB;AACpC,aAAKjG,OAAOY,MAAM,wCAAA;AAClB,cAAM,IAAIC,MAAM,+BAAA;MAClB;AACA,UAAIyD,eAAeC,yBAAY;AAC7B,YAAID,IAAIE,eAAe,KAAK;AAC1B,eAAKxE,OAAOY,MAAM,0CAAA;AAClB,gBAAM,IAAIC,MAAM,2CAAA;QAClB;AACA,YAAIyD,IAAIE,eAAe,KAAK;AAC1B,eAAKxE,OAAOY,MAAM,kDAAA;AAClB,gBAAM,IAAIC,MAAM,qCAAA;QAClB;AACA,YAAIyD,IAAIE,eAAe,KAAK;AAC1B,eAAKxE,OAAOY,MAAM,6BAA6B0D,IAAI4B,OAAO;AAC1D,gBAAM,IAAIrF,MAAM,6BAA6ByD,IAAI4B,OAAO,EAAE;QAC5D;AACA,aAAKlG,OAAOY,MAAM,mBAAmB0D,IAAIE,UAAU,KAAKF,IAAI4B,OAAO;AACnE,cAAM,IAAIrF,MAAM,yBAAyByD,IAAI4B,OAAO,EAAE;MACxD;AACA,YAAM5B;IACR;EACF;AACF;;;;;;;;;;;;;;;;;AD/lBO,IAAM6B,cAAN,MAAMA;SAAAA;;;AAAa;;;;IAJxBC,SAAS;MAACC;;IACVC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;","names":["import_common","import_config","EMAIL_SANS","EMAIL_MONO","VRITTI_CLOUD_LOGO_LIGHT","VRITTI_CLOUD_LOGO_DARK","EMAIL_NOTICE_TONES","warn","background","color","boxClass","textClass","danger","success","EMAIL_BUTTON_TONES","primary","EmailService","logger","Logger","name","brevoClient","senderEmail","senderName","logoLightUrl","logoDarkUrl","frontendBaseUrl","configService","apiKey","get","error","Error","BrevoClient","maxRetries","replace","log","sendVerificationEmail","email","otp","expiresAt","displayName","expiry","formatExpiry","htmlContent","renderEmailShell","preheader","heading","body","emailText","emailCode","emailNotice","emailDivider","emailMuted","join","textContent","renderTextShell","sendEmail","to","subject","sendPasswordResetEmail","sendEmailChangeNotification","oldEmail","newEmail","revertToken","revertExpiresAt","hoursUntilExpiry","Math","floor","getTime","Date","now","window","pluralize","revertLink","requireFrontendBaseUrl","emailFields","label","value","emailButton","sendEmailRevertConfirmation","sendInviteEmail","params","inviteUrl","sendTransactionalEmail","verifyConnection","transactionalEmails","sendTransacEmail","sender","err","BrevoError","statusCode","renderEmailBrand","trim","blocks","footer","html","code","tone","center","palette","typography","fields","rows","map","field","index","length","href","imgStyle","minutes","ceil","emailData","result","debug","messageId","BrevoTimeoutError","message","EmailModule","imports","ConfigModule","providers","EmailService","exports"]}
1
+ {"version":3,"sources":["../src/email/index.ts","../src/email/email.module.ts","../src/email/email.service.ts","../src/pluralize.ts"],"sourcesContent":["// Email module — Brevo-backed transactional email\nexport { EmailModule } from './email.module';\nexport { EmailService } from './email.service';\n","import { Global, Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { EmailService } from './email.service';\n\n@Global()\n@Module({\n imports: [ConfigModule],\n providers: [EmailService],\n exports: [EmailService],\n})\nexport class EmailModule {}\n","import { BrevoClient, BrevoError, BrevoTimeoutError } from '@getbrevo/brevo';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { pluralize } from '../pluralize';\n\n// Email-safe font stacks. Web fonts only land in clients that allow them; the fallbacks carry the rest.\nconst EMAIL_SANS = \"'IBM Plex Sans',Helvetica,Arial,sans-serif\";\nconst EMAIL_MONO = \"'JetBrains Mono','Courier New',Courier,monospace\";\n\n// Brand marks inlined from apps/cloud-web/src/assets/vritti_cloud_{light,dark}.svg, sized for the email\n// header (viewBox 407x67 -> 170x28). Inline SVG renders in Apple Mail and iOS Mail; Gmail, Outlook and\n// Yahoo strip <svg> from email bodies, so configure EMAIL_LOGO_LIGHT_URL with a hosted PNG to reach those.\nconst VRITTI_CLOUD_LOGO_LIGHT = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_56_379)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_56_379)\"/><path d=\"M35.0507 14C37.2595 14.0004 39.0507 15.7911 39.0507 18C39.0507 20.2089 37.2595 21.9996 35.0507 22C34.4331 21.9999 33.8472 21.8594 33.3251 21.6094C33.1714 21.6936 33.0228 21.829 32.8553 22.0293L24.3583 35.0234C24.2882 35.1904 24.2274 35.3388 24.1805 35.4658C24.1331 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8587 24.099 35.9018 24.1229 35.9238C24.1488 35.9469 24.2031 35.9626 24.3085 35.9502C26.1439 35.5539 27.4169 35.1177 28.4637 34.5029C29.5109 33.8878 30.3371 33.0905 31.2772 31.9678L33.3827 28.5957C33.1697 28.1068 33.0507 27.5673 33.0507 27C33.0507 24.791 34.8417 23.0002 37.0507 23C39.2597 23.0002 41.0507 24.791 41.0507 27C41.0507 29.0504 39.5075 30.7394 37.5194 30.9717C37.459 30.9962 37.397 31.0224 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1076 30.9977 37.0789 31 37.0507 31C36.4652 30.9999 35.9092 30.8725 35.4081 30.6465C35.0278 30.6575 34.6938 30.7719 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2336 51.6277 21.1425 51.6842 21.0507 51.6836C20.9809 51.6826 20.9167 51.6494 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5887 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3412 48.8997 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.0079 39.1768 20.0624 38.9248C20.117 38.6719 20.1966 38.3975 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0507 18.6002 31.0507 18C31.0507 15.7911 32.8418 14.0004 35.0507 14ZM36.9999 25C35.8954 25.0001 34.9999 25.8955 34.9999 27C34.9999 28.1045 35.8954 28.9999 36.9999 29C38.1043 28.9999 38.9999 28.1045 38.9999 27C38.9999 25.8955 38.1043 25.0001 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_56_379)\"/><defs><linearGradient id=\"paint0_linear_56_379\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient><linearGradient id=\"paint1_linear_56_379\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_56_379\" x1=\"30.5245\" y1=\"14\" x2=\"30.5245\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient></defs></svg>`;\n\nconst VRITTI_CLOUD_LOGO_DARK = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_92_90)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_92_90)\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M35.0497 14C37.2588 14 39.0497 15.7909 39.0497 18C39.0497 20.2091 37.2588 22 35.0497 22C34.4321 21.9998 33.8463 21.8595 33.3241 21.6094C33.1708 21.6937 33.0224 21.8295 32.8554 22.0293L24.3583 35.0234C24.2882 35.1903 24.2274 35.3389 24.1805 35.4658C24.1332 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8586 24.099 35.9018 24.1229 35.9238C24.1489 35.9468 24.2034 35.9625 24.3085 35.9502C26.1438 35.5539 27.4169 35.1177 28.4638 34.5029C29.5108 33.8878 30.3372 33.0905 31.2772 31.9678L33.3817 28.5967C33.1686 28.1076 33.0497 27.5675 33.0497 27C33.0497 24.7912 34.841 23.0006 37.0497 23C39.2588 23 41.0497 24.7909 41.0497 27C41.0497 29.0499 39.5078 30.7378 37.5204 30.9707C37.4596 30.9954 37.3974 31.0223 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1074 30.9977 37.0781 31 37.0497 31C36.4643 30.9999 35.9082 30.8726 35.4071 30.6465C35.0273 30.6577 34.6935 30.7722 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2337 51.6276 21.1424 51.6841 21.0507 51.6836C20.981 51.6826 20.9167 51.6493 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5888 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3413 48.8996 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.008 39.1768 20.0624 38.9248C20.117 38.672 20.1966 38.3974 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0497 18.6003 31.0497 18C31.0497 15.7912 32.841 14.0006 35.0497 14ZM36.9999 25C35.8955 25.0002 34.9999 25.8956 34.9999 27C34.9999 28.1044 35.8955 28.9998 36.9999 29C38.1042 28.9998 38.9999 28.1044 38.9999 27C38.9999 25.8956 38.1042 25.0002 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_92_90)\"/><defs><linearGradient id=\"paint0_linear_92_90\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient><linearGradient id=\"paint1_linear_92_90\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_92_90\" x1=\"30.5246\" y1=\"14\" x2=\"30.5246\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient></defs></svg>`;\n\ntype EmailNoticeTone = 'warn' | 'danger' | 'success';\ntype EmailButtonTone = 'primary' | 'danger';\n\nconst EMAIL_NOTICE_TONES: Record<\n EmailNoticeTone,\n { background: string; color: string; boxClass: string; textClass: string }\n> = {\n warn: { background: '#fdf6e7', color: '#8a6410', boxClass: 'e-warnbox', textClass: 'e-warn' },\n danger: { background: '#fdeceb', color: '#a32b21', boxClass: 'e-dangerbox', textClass: 'e-danger' },\n success: { background: '#eaf6ee', color: '#1c6b3f', boxClass: 'e-successbox', textClass: 'e-success' },\n};\n\nconst EMAIL_BUTTON_TONES: Record<EmailButtonTone, string> = {\n primary: '#1b74d1',\n danger: '#c0392b',\n};\n\n@Injectable()\nexport class EmailService {\n private readonly logger = new Logger(EmailService.name);\n private readonly brevoClient: BrevoClient;\n private readonly senderEmail: string;\n private readonly senderName: string;\n private readonly logoLightUrl?: string;\n private readonly logoDarkUrl?: string;\n private readonly frontendBaseUrl?: string;\n\n constructor(private readonly configService: ConfigService) {\n const apiKey = this.configService.get<string>('BREVO_API_KEY');\n\n if (!apiKey) {\n this.logger.error('BREVO_API_KEY is not configured. Email sending will fail.');\n throw new Error('Email service configuration error: Missing BREVO_API_KEY');\n }\n\n // Initialize Brevo client with built-in retry support\n this.brevoClient = new BrevoClient({ apiKey, maxRetries: 3 });\n\n // Get sender configuration\n const senderEmail = this.configService.get<string>('SENDER_EMAIL');\n const senderName = this.configService.get<string>('SENDER_NAME');\n\n if (!senderEmail || !senderName) {\n this.logger.error('Sender email or name is not configured.');\n throw new Error('Email service configuration error: Missing SENDER_EMAIL or SENDER_NAME');\n }\n\n this.senderEmail = senderEmail;\n this.senderName = senderName;\n\n // Hosted brand marks for email headers. Must be PNG - most clients block SVG.\n this.logoLightUrl = this.configService.get<string>('EMAIL_LOGO_LIGHT_URL');\n this.logoDarkUrl = this.configService.get<string>('EMAIL_LOGO_DARK_URL') || this.logoLightUrl;\n\n if (!this.logoLightUrl) {\n this.logger.warn(\n 'EMAIL_LOGO_LIGHT_URL is not configured. Emails fall back to an inline SVG wordmark, which Gmail, Outlook and Yahoo strip.',\n );\n }\n\n // Base URL for links back into the web app. Optional at boot so consumers that send no\n // link-bearing emails still start; the senders that need it throw with a clear message.\n this.frontendBaseUrl = this.configService.get<string>('FRONTEND_BASE_URL')?.replace(/\\/+$/, '');\n\n if (!this.frontendBaseUrl) {\n this.logger.warn('FRONTEND_BASE_URL is not configured. Emails containing web app links will fail to send.');\n }\n\n this.logger.log('Brevo email service initialized successfully');\n }\n\n // Sends an email verification OTP to the given recipient\n async sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your verification code is ${otp}. It expires in ${expiry}.`,\n heading: 'Verify Your Email',\n body: [\n this.emailText(`Hello ${name} &mdash; enter the code below to finish setting up your Vritti AI Cloud account.`),\n this.emailCode('Verification Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'Enter the code below to finish setting up your Vritti AI Cloud account.',\n `Verification Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Verify Your Email - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Verification email sent to ${email}`);\n }\n\n // Sends a password reset OTP to the given recipient\n async sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your password reset code is ${otp}. It expires in ${expiry}.`,\n heading: 'Reset Your Password',\n body: [\n this.emailText(\n `Hello ${name} &mdash; we received a request to reset your password. Enter the code below to choose a new one.`,\n ),\n this.emailCode('Reset Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailNotice(\n '<strong>Never share this code.</strong> Vritti will never ask you for it by email, phone or chat.',\n 'danger',\n ),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request a reset? You can ignore this email &mdash; your password stays unchanged and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'We received a request to reset your password. Enter the code below to choose a new one.',\n `Reset Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n 'Never share this code. Vritti will never ask you for it by email, phone or chat.',\n \"Didn't request a reset? You can ignore this email - your password stays unchanged and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Reset Your Password - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Password reset email sent to ${email}`);\n }\n\n // Sends an email change notification to the old address with a revert link\n async sendEmailChangeNotification(\n oldEmail: string,\n newEmail: string,\n revertToken: string,\n revertExpiresAt: Date,\n displayName?: string,\n ): Promise<void> {\n const name = displayName || 'there';\n const hoursUntilExpiry = Math.floor((revertExpiresAt.getTime() - Date.now()) / 3_600_000);\n const window = `${hoursUntilExpiry} ${pluralize('hour', hoursUntilExpiry)}`;\n\n const revertLink = `${this.requireFrontendBaseUrl()}/settings/profile/revert-email?token=${revertToken}`;\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is now ${newEmail}.`,\n heading: 'Your Email Address Changed',\n body: [\n this.emailText(`Hello ${name} &mdash; the email address on your Vritti AI Cloud account has been changed.`),\n this.emailFields([\n { label: 'Previous Email', value: oldEmail },\n { label: 'New Email', value: newEmail },\n ]),\n this.emailNotice(\n `<strong>Didn't make this change?</strong> You can revert it within the next ${window}.`,\n 'danger',\n ),\n this.emailButton(revertLink, 'Revert Email Change', 'danger'),\n this.emailDivider(),\n this.emailMuted('If you made this change yourself, no action is needed &mdash; you can ignore this email.'),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The email address on your Vritti AI Cloud account has been changed.',\n `Previous Email: ${oldEmail}`,\n `New Email: ${newEmail}`,\n `Didn't make this change? You can revert it within the next ${window}:`,\n revertLink,\n 'If you made this change yourself, no action is needed - you can ignore this email.',\n ]);\n\n await this.sendEmail({\n to: [{ email: oldEmail, name }],\n subject: 'Your Email Address Has Been Changed - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email change notification sent to ${oldEmail}`);\n }\n\n // Sends a confirmation to the restored email address after a revert\n async sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is back to ${email}.`,\n heading: 'Email Change Reverted',\n body: [\n this.emailText(\n `Hello ${name} &mdash; the recent change to your email address has been reverted. Your account email is now:`,\n ),\n this.emailNotice(email, 'success', true),\n this.emailDivider(),\n this.emailMuted(\"Didn't request this revert? Contact our support team immediately.\"),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The recent change to your email address has been reverted. Your account email is now:',\n email,\n \"Didn't request this revert? Contact our support team immediately.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Email Address Change Reverted - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email revert confirmation sent to ${email}`);\n }\n\n // Sends an invite email to a new portal user with their set-password link\n async sendInviteEmail(params: { to: string; name: string; inviteUrl: string }): Promise<void> {\n const { to, name, inviteUrl } = params;\n\n const htmlContent = this.renderEmailShell({\n preheader: 'Set your password to activate your Vritti AI Cloud account.',\n heading: \"You're Invited\",\n body: [\n this.emailText(\n `Hello ${name} &mdash; you've been invited to join Vritti AI Cloud. Set your password to activate your account.`,\n ),\n this.emailButton(inviteUrl, 'Set Your Password'),\n this.emailNotice('This invite link is single-use and expires once your password is set.', 'warn'),\n this.emailDivider(),\n this.emailMuted(\n `If the button doesn't work, copy this link into your browser:<br><span style=\"word-break:break-all;\">${inviteUrl}</span>`,\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n \"You've been invited to join Vritti AI Cloud. Visit the link below to set your password and activate your account:\",\n inviteUrl,\n 'This invite link is single-use and expires once your password is set.',\n ]);\n\n await this.sendEmail({\n to: [{ email: to, name }],\n subject: 'You have been invited to Vritti AI',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Invite email sent to ${to}`);\n }\n\n // Sends a transactional email with custom subject, HTML, and text content\n async sendTransactionalEmail(params: {\n to: { email: string; name?: string };\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n await this.sendEmail({\n to: [params.to],\n subject: params.subject,\n htmlContent: params.htmlContent,\n textContent: params.textContent,\n });\n this.logger.log(`Transactional email sent to ${params.to.email}`);\n }\n\n // Verifies Brevo API connectivity — a 400 response means the API is reachable\n async verifyConnection(): Promise<boolean> {\n try {\n await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: [{ email: this.senderEmail }],\n subject: 'Connection Test',\n htmlContent: '<p>Test</p>',\n });\n return true;\n } catch (err) {\n // A 400 error means the API is reachable but params are incomplete — still a successful connection test\n if (err instanceof BrevoError && err.statusCode === 400) {\n return true;\n }\n this.logger.error('Brevo connection verification failed:', err);\n return false;\n }\n }\n\n // Sends a transactional email via Brevo — retries handled internally by BrevoClient\n // Renders the shared transactional email shell: brand header, card, heading, body rows, footer\n private renderEmailShell(params: { preheader: string; heading: string; body: string }): string {\n const { preheader, heading, body } = params;\n\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"x-apple-disable-message-reformatting\">\n<meta name=\"color-scheme\" content=\"light dark\">\n<meta name=\"supported-color-schemes\" content=\"light dark\">\n<title>${heading}</title>\n<!--[if mso]>\n<style>body,table,td,a{font-family:Arial,Helvetica,sans-serif !important;}</style>\n<![endif]-->\n<style>\n @media only screen and (max-width:620px){\n .m-pad{padding-left:24px !important;padding-right:24px !important;}\n .m-code{font-size:32px !important;letter-spacing:8px !important;}\n .m-h1{font-size:26px !important;line-height:32px !important;}\n }\n @media (prefers-color-scheme:dark){\n .e-bg{background-color:#0f1214 !important;}\n .e-card{background-color:#1c2126 !important;border-color:#2b3238 !important;}\n .e-h1{color:#f0f3f5 !important;}\n .e-body{color:#a3adb8 !important;}\n .e-panel{background-color:#14181c !important;border-color:#2b3238 !important;}\n .e-label{color:#8b96a2 !important;}\n .e-code{color:#63a8ee !important;}\n .e-value{color:#f0f3f5 !important;}\n .e-warnbox{background-color:#2a2317 !important;}\n .e-warn{color:#e0b45e !important;}\n .e-dangerbox{background-color:#2b1a18 !important;}\n .e-danger{color:#ef8a7f !important;}\n .e-successbox{background-color:#16261c !important;}\n .e-success{color:#5fc98a !important;}\n .e-rule{background-color:#2b3238 !important;}\n .e-muted{color:#8b96a2 !important;}\n .e-footer{color:#79838e !important;}\n .e-logo-light{display:none !important;}\n .e-logo-dark{display:block !important;}\n }\n</style>\n</head>\n<body class=\"e-bg\" style=\"margin:0;padding:0;background-color:#f7f9fb;\">\n<span style=\"display:none!important;visibility:hidden;opacity:0;color:transparent;height:0;width:0;overflow:hidden;mso-hide:all;\">${preheader}</span>\n\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-bg\" style=\"background-color:#f7f9fb;\">\n <tr>\n <td align=\"center\" style=\"padding:40px 12px 56px 12px;\">\n\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"600\" style=\"width:600px;max-width:600px;\">\n\n <tr>\n <td style=\"padding:0 0 20px 2px;\">\n ${this.renderEmailBrand()}\n </td>\n </tr>\n\n <tr>\n <td class=\"e-card\" style=\"background-color:#ffffff;border:1px solid #e6ebf2;border-radius:12px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n\n <tr>\n <td class=\"m-pad m-h1 e-h1\" style=\"padding:40px 40px 0 40px;font-family:${EMAIL_SANS};font-size:28px;line-height:34px;mso-line-height-rule:exactly;color:#1b2434;font-weight:600;letter-spacing:-0.4px;\">\n ${heading}\n </td>\n </tr>\n${body}\n <tr><td style=\"height:36px;line-height:36px;font-size:0;\">&nbsp;</td></tr>\n\n </table>\n </td>\n </tr>\n\n <tr>\n <td class=\"m-pad e-footer\" style=\"padding:22px 2px 0 2px;font-family:${EMAIL_SANS};font-size:12px;line-height:20px;mso-line-height-rule:exactly;color:#8a95a5;letter-spacing:0.01em;\">\n Vritti AI Cloud<br>\n Automated message &mdash; replies aren't monitored.\n </td>\n </tr>\n\n </table>\n\n </td>\n </tr>\n</table>\n</body>\n</html>\n `.trim();\n }\n\n // Builds the plain-text alternative from the same blocks the HTML shell renders\n private renderTextShell(blocks: string[]): string {\n const footer = ['---', 'Vritti AI Cloud', \"Automated message - replies aren't monitored.\"].join('\\n');\n return [...blocks, footer].join('\\n\\n');\n }\n\n // Body paragraph\n private emailText(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-body\" style=\"padding:14px 40px 0 40px;font-family:${EMAIL_SANS};font-size:15px;line-height:25px;mso-line-height-rule:exactly;color:#5d6b80;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Monospace OTP tile with an uppercase label\n private emailCode(label: string, code: string): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">\n <tr>\n <td align=\"center\" class=\"e-label\" style=\"padding:18px 16px 6px 16px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${label}\n </td>\n </tr>\n <tr>\n <td align=\"center\" class=\"m-code e-code\" style=\"padding:0 16px 20px 16px;font-family:${EMAIL_MONO};font-size:38px;line-height:46px;mso-line-height-rule:exactly;letter-spacing:11px;color:#1b74d1;font-weight:600;text-indent:11px;\">\n ${code}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Tinted callout. `center` renders the content as a centred mono value instead of a sentence\n private emailNotice(html: string, tone: EmailNoticeTone, center = false): string {\n const palette = EMAIL_NOTICE_TONES[tone];\n const typography = center\n ? `font-family:${EMAIL_MONO};font-size:17px;line-height:26px;font-weight:600;`\n : `font-family:${EMAIL_SANS};font-size:13px;line-height:19px;letter-spacing:0.01em;`;\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:16px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"${palette.boxClass}\" style=\"background-color:${palette.background};border-radius:8px;\">\n <tr>\n <td align=\"${center ? 'center' : 'left'}\" class=\"${palette.textClass}\" style=\"padding:${center ? '16px 14px' : '11px 14px'};${typography}mso-line-height-rule:exactly;color:${palette.color};\">\n ${html}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Label/value list rendered with monospace values\n private emailFields(fields: { label: string; value: string }[]): string {\n const rows = fields\n .map(\n (field, index) => `\n <tr>\n <td class=\"e-label\" style=\"padding:${index === 0 ? '18px' : '14px'} 18px 0 18px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${field.label}\n </td>\n </tr>\n <tr>\n <td class=\"e-value\" style=\"padding:4px 18px ${index === fields.length - 1 ? '18px' : '0'} 18px;font-family:${EMAIL_MONO};font-size:15px;line-height:23px;mso-line-height-rule:exactly;color:#1b2434;word-break:break-all;\">\n ${field.value}\n </td>\n </tr>`,\n )\n .join('');\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">${rows}\n </table>\n </td>\n </tr>`;\n }\n\n // Solid CTA button. Colours are theme-independent so the fill reads the same in light and dark\n private emailButton(href: string, label: string, tone: EmailButtonTone = 'primary'): string {\n const background = EMAIL_BUTTON_TONES[tone];\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:24px 40px 0 40px;\">\n <!--[if mso]>\n <v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:240px;\" arcsize=\"20%\" stroke=\"f\" fillcolor=\"${background}\">\n <w:anchorlock/>\n <center style=\"color:#ffffff;font-family:Arial,sans-serif;font-size:15px;font-weight:600;\">${label}</center>\n </v:roundrect>\n <![endif]-->\n <!--[if !mso]><!-->\n <a href=\"${href}\" style=\"display:inline-block;padding:13px 28px;background-color:${background};color:#ffffff;text-decoration:none;border-radius:8px;font-family:${EMAIL_SANS};font-size:15px;line-height:18px;font-weight:600;letter-spacing:0.01em;\">${label}</a>\n <!--<![endif]-->\n </td>\n </tr>`;\n }\n\n // Hairline rule separating the body from the closing note\n private emailDivider(): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n <tr><td height=\"1\" class=\"e-rule\" style=\"height:1px;line-height:1px;font-size:0;background-color:#eef2f7;\">&nbsp;</td></tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Small closing note below the divider\n private emailMuted(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-muted\" style=\"padding:18px 40px 0 40px;font-family:${EMAIL_SANS};font-size:13px;line-height:21px;mso-line-height-rule:exactly;color:#7b8798;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Renders the email header brand mark: a hosted logo when configured, inline SVG otherwise\n private renderEmailBrand(): string {\n const imgStyle = 'border:0;outline:none;text-decoration:none;width:170px;height:auto;';\n\n // A hosted PNG is preferred: it is the only form Gmail, Outlook and Yahoo will render\n if (this.logoLightUrl) {\n return `<img src=\"${this.logoLightUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-light\" style=\"display:block;${imgStyle}\">\n <img src=\"${this.logoDarkUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-dark\" style=\"display:none;${imgStyle}\">`;\n }\n\n // Both marks ship; the dark one is revealed by the prefers-color-scheme rule\n return `<div class=\"e-logo-light\" style=\"display:block;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_LIGHT}</div>\n <div class=\"e-logo-dark\" style=\"display:none;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_DARK}</div>`;\n }\n\n // Frontend base URL for emails that link back into the web app\n private requireFrontendBaseUrl(): string {\n if (!this.frontendBaseUrl) {\n throw new Error('Email service configuration error: Missing FRONTEND_BASE_URL');\n }\n return this.frontendBaseUrl;\n }\n\n // Renders an OTP expiry window as a pluralised minute count\n private formatExpiry(expiresAt: Date): string {\n const minutes = Math.ceil((expiresAt.getTime() - Date.now()) / 60_000);\n return `${minutes} ${pluralize('minute', minutes)}`;\n }\n private async sendEmail(emailData: {\n to: Array<{ email: string; name?: string }>;\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n try {\n const result = await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: emailData.to,\n subject: emailData.subject,\n htmlContent: emailData.htmlContent,\n textContent: emailData.textContent,\n });\n this.logger.debug(`Email sent successfully. Message ID: ${result.messageId}`);\n } catch (err) {\n if (err instanceof BrevoTimeoutError) {\n this.logger.error('Brevo request timed out after retries.');\n throw new Error('Email sending failed: timeout');\n }\n if (err instanceof BrevoError) {\n if (err.statusCode === 429) {\n this.logger.error('Brevo rate limit exceeded after retries.');\n throw new Error('Email sending failed: rate limit exceeded');\n }\n if (err.statusCode === 401) {\n this.logger.error('Brevo authentication failed. Check your API key.');\n throw new Error('Email service authentication failed');\n }\n if (err.statusCode === 400) {\n this.logger.error('Bad request to Brevo API:', err.message);\n throw new Error(`Invalid email parameters: ${err.message}`);\n }\n this.logger.error(`Brevo API error ${err.statusCode}:`, err.message);\n throw new Error(`Email sending failed: ${err.message}`);\n }\n throw err;\n }\n }\n}\n","// Re-export pluralize-esm's default export as the subpath entry point\nexport { default as pluralize } from 'pluralize-esm';\n"],"mappings":"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","names":["import_common","import_config","EMAIL_SANS","EMAIL_MONO","VRITTI_CLOUD_LOGO_LIGHT","VRITTI_CLOUD_LOGO_DARK","EMAIL_NOTICE_TONES","warn","background","color","boxClass","textClass","danger","success","EMAIL_BUTTON_TONES","primary","EmailService","logger","Logger","name","brevoClient","senderEmail","senderName","logoLightUrl","logoDarkUrl","frontendBaseUrl","configService","apiKey","get","error","Error","BrevoClient","maxRetries","replace","log","sendVerificationEmail","email","otp","expiresAt","displayName","expiry","formatExpiry","htmlContent","renderEmailShell","preheader","heading","body","emailText","emailCode","emailNotice","emailDivider","emailMuted","join","textContent","renderTextShell","sendEmail","to","subject","sendPasswordResetEmail","sendEmailChangeNotification","oldEmail","newEmail","revertToken","revertExpiresAt","hoursUntilExpiry","Math","floor","getTime","Date","now","window","pluralize","revertLink","requireFrontendBaseUrl","emailFields","label","value","emailButton","sendEmailRevertConfirmation","sendInviteEmail","params","inviteUrl","sendTransactionalEmail","verifyConnection","transactionalEmails","sendTransacEmail","sender","err","BrevoError","statusCode","renderEmailBrand","trim","blocks","footer","html","code","tone","center","palette","typography","fields","rows","map","field","index","length","href","imgStyle","minutes","ceil","emailData","result","debug","messageId","BrevoTimeoutError","message","EmailModule","imports","ConfigModule","providers","EmailService","exports"]}
package/dist/email.js CHANGED
@@ -16,13 +16,22 @@ import { default as default2 } from "pluralize-esm";
16
16
  // src/email/email.service.ts
17
17
  function _ts_decorate(decorators, target, key, desc) {
18
18
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
19
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
20
+ r = Reflect.decorate(decorators, target, key, desc);
21
+ } else {
22
+ for (var i = decorators.length - 1; i >= 0; i--) {
23
+ if (d = decorators[i]) {
24
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
25
+ }
26
+ }
27
+ }
21
28
  return c > 3 && r && Object.defineProperty(target, key, r), r;
22
29
  }
23
30
  __name(_ts_decorate, "_ts_decorate");
24
- function _ts_metadata(k, v) {
25
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
31
+ function _ts_metadata(metadataKey, metadataValue) {
32
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
33
+ return Reflect.metadata(metadataKey, metadataValue);
34
+ }
26
35
  }
27
36
  __name(_ts_metadata, "_ts_metadata");
28
37
  var EMAIL_SANS = "'IBM Plex Sans',Helvetica,Arial,sans-serif";
@@ -599,8 +608,15 @@ EmailService = _ts_decorate([
599
608
  // src/email/email.module.ts
600
609
  function _ts_decorate2(decorators, target, key, desc) {
601
610
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
602
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
603
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
611
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
612
+ r = Reflect.decorate(decorators, target, key, desc);
613
+ } else {
614
+ for (var i = decorators.length - 1; i >= 0; i--) {
615
+ if (d = decorators[i]) {
616
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
617
+ }
618
+ }
619
+ }
604
620
  return c > 3 && r && Object.defineProperty(target, key, r), r;
605
621
  }
606
622
  __name(_ts_decorate2, "_ts_decorate");