@venizia/ignis-docs 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -1,6 +1,107 @@
1
- # Mail -- API Reference
1
+ ---
2
+ title: Mail Component - Full Reference
3
+ description: Binding keys, configuration options, interfaces, and internal implementation of the Mail component
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # Mail Component Reference
8
+
9
+ Every binding key, configuration variant, interface, and internal mechanism of `MailComponent`. For the task-oriented walkthrough, see [Usage & Examples](./usage).
10
+
11
+ **Files:**
12
+
13
+ - [`packages/core/src/components/mail/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/component.ts)
14
+ - [`packages/core/src/components/mail/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/types.ts)
15
+ - [`packages/core/src/components/mail/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/constants.ts)
16
+ - [`packages/core/src/components/mail/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/keys.ts)
17
+ - [`packages/core/src/components/mail/services/mail.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/services/mail.service.ts)
18
+ - [`packages/core/src/components/mail/services/template.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/services/template.service.ts)
19
+ - [`packages/core/src/components/mail/services/generator.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/services/generator.service.ts)
20
+ - [`packages/core/src/components/mail/providers/mail-transporter.provider.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/providers/mail-transporter.provider.ts)
21
+ - [`packages/core/src/components/mail/providers/mail-queue-executor.provider.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/providers/mail-queue-executor.provider.ts)
22
+ - [`packages/core/src/components/mail/helpers/transporters/nodemail-transporter.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/transporters/nodemail-transporter.helper.ts)
23
+ - [`packages/core/src/components/mail/helpers/transporters/mailgun-transporter.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/transporters/mailgun-transporter.helper.ts)
24
+ - [`packages/core/src/components/mail/helpers/executors/direct-executor.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/executors/direct-executor.helper.ts)
25
+ - [`packages/core/src/components/mail/helpers/executors/internal-queue-executor.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/executors/internal-queue-executor.helper.ts)
26
+ - [`packages/core/src/components/mail/helpers/executors/bull-mq-executor.helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/executors/bull-mq-executor.helper.ts)
27
+ - [`packages/core/src/components/mail/utilities/type.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/utilities/type.utility.ts)
28
+ - [`packages/core/src/components/mail/utilities/verification.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/utilities/verification.utility.ts)
29
+
30
+ ## Quick reference
31
+
32
+ | Item | Value |
33
+ |------|-------|
34
+ | Package | `@venizia/ignis` |
35
+ | Subpath | `@venizia/ignis/mail` |
36
+ | Component class | `MailComponent` |
37
+ | Runtimes | Both (Bun, Node.js) |
38
+
39
+ | Component | Purpose |
40
+ |-----------|---------|
41
+ | `MailComponent` | Registers mail services, transporters, generators, and the queue executor |
42
+ | `MailService` | `send()`, `sendBatch()`, `sendTemplate()`, `verify()` |
43
+ | `TemplateEngineService` | <code v-pre>{{variable}}</code> substitution engine, in-memory template registry |
44
+ | `NodemailerTransportHelper` | SMTP transport via `nodemailer` |
45
+ | `MailgunTransportHelper` | Mailgun HTTP API transport via `mailgun.js` |
46
+ | `DirectMailExecutorHelper` | Runs the processor immediately, no queue |
47
+ | `InternalQueueMailExecutorHelper` | In-memory queue (`SequentialQueueHelper`) |
48
+ | `BullMQMailExecutorHelper` | Redis-backed queue, distributed workers |
49
+ | `MailTransportProvider` | Factory: `TMailOptions` -> `IMailTransport` |
50
+ | `MailQueueExecutorProvider` | Factory: `IMailQueueExecutorConfig` -> `IMailQueueExecutor` |
51
+ | `NumericCodeGenerator` | Cryptographically random numeric codes |
52
+ | `RandomTokenGenerator` | Cryptographically random base64url tokens |
53
+ | `DefaultVerificationDataGenerator` | Composes both generators into an `IVerificationData` |
54
+
55
+ ## Import paths
2
56
 
3
- > Architecture, interfaces, and internal implementation details of the Mail component.
57
+ ```typescript
58
+ import {
59
+ MailComponent,
60
+ MailKeys,
61
+ MailProviders,
62
+ MailErrorCodes,
63
+ MailDefaults,
64
+ MailExecutorErrors,
65
+ MailQueueExecutorTypes,
66
+ BullMQExecutorModes,
67
+ MailService,
68
+ TemplateEngineService,
69
+ NumericCodeGenerator,
70
+ RandomTokenGenerator,
71
+ DefaultVerificationDataGenerator,
72
+ MailTransportProvider,
73
+ MailQueueExecutorProvider,
74
+ } from '@venizia/ignis/mail';
75
+
76
+ import type {
77
+ TMailOptions,
78
+ IBaseMailOptions,
79
+ INodemailerMailOptions,
80
+ IMailgunMailOptions,
81
+ ICustomMailOptions,
82
+ IGenericMailOptions,
83
+ IMailService,
84
+ IMailTemplateEngine,
85
+ IMailMessage,
86
+ IMailSendResult,
87
+ IMailTransport,
88
+ IMailAttachment,
89
+ IMailQueueExecutor,
90
+ IMailQueueExecutorConfig,
91
+ IMailQueueOptions,
92
+ IMailQueueResult,
93
+ IMailProcessorResult,
94
+ ITemplate,
95
+ IVerificationCodeGenerator,
96
+ IVerificationTokenGenerator,
97
+ IVerificationDataGenerator,
98
+ IVerificationData,
99
+ IVerificationGenerationOptions,
100
+ TMailProvider,
101
+ TNodemailerConfig,
102
+ TMailgunConfig,
103
+ } from '@venizia/ignis/mail';
104
+ ```
4
105
 
5
106
  ## Architecture
6
107
 
@@ -8,126 +109,285 @@
8
109
  ┌─────────────────────────────────────────────────┐
9
110
  │ Your Application │
10
111
  │ │
11
- NodemailerComponent (wrapper)
12
- │ ├── binds MailKeys.MAIL_OPTIONS
112
+ preConfigure()
113
+ │ ├── binds MailKeys.MAIL_OPTIONS (required)
13
114
  │ ├── binds MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG │
115
+ │ │ (optional -- defaults to `direct`) │
14
116
  │ └── registers MailComponent │
15
117
  └───────────────────────┬─────────────────────────┘
16
118
 
17
119
 
18
120
  ┌─────────────────────────────────────────────────┐
19
- │ MailComponent
121
+ │ MailComponent.binding()
20
122
  │ │
21
- binding()
22
- │ ├── initGenerators() │
123
+ ├── initGenerators() (transient bindings)
23
124
  │ │ ├── NumericCodeGenerator │
24
125
  │ │ ├── RandomTokenGenerator │
25
126
  │ │ └── DefaultVerificationDataGenerator │
26
127
  │ │ │
27
- │ ├── initProviders()
28
- │ │ ├── MailTransportProvider (singleton)
29
- │ │ └── MailQueueExecutorProvider (singleton)
128
+ │ ├── initProviders() (singleton bindings)
129
+ │ │ ├── MailTransportProvider
130
+ │ │ └── MailQueueExecutorProvider
30
131
  │ │ │
31
- │ ├── initServices()
32
- │ │ ├── MailService (singleton)
33
- │ │ └── TemplateEngineService (singleton)
132
+ │ ├── initServices() (singleton bindings)
133
+ │ │ ├── MailService
134
+ │ │ └── TemplateEngineService
34
135
  │ │ │
35
136
  │ └── createAndBindInstances() │
36
- │ ├── Transport Instance ◄── MAIL_OPTIONS │
37
- │ └── Queue Executor ◄── QUEUE_CONFIG
137
+ │ ├── Transport Instance ◄── MAIL_OPTIONS │
138
+ │ └── Queue Executor ◄── QUEUE_CONFIG
139
+ │ (or the direct │
140
+ │ default) │
38
141
  └─────────────────────────────────────────────────┘
39
142
  ```
40
143
 
41
- **Architecture Components:**
144
+ `MailService` and the queue executor are **independent** consumers of the transport/config -- `IMailQueueExecutor` never calls `MailService`. See [How it works](./#how-it-works) on the Overview for that distinction.
42
145
 
43
- - **`MailComponent`** -- Initializes and registers all mail services, transporters, and queue executors. Extends `BaseComponent`. Validates that `MailKeys.MAIL_OPTIONS` is bound before proceeding
44
- - **`MailService`** -- Extends `BaseService`. Provides `send()`, `sendBatch()`, `sendTemplate()`, and `verify()`. Injects transport instance and template engine. Validates messages before sending
45
- - **`TemplateEngineService`** -- Extends `BaseService`. Manages email templates with simple <code v-pre>{{variable}}</code> substitution. Stores templates in an in-memory `Map`
46
- - **`MailTransportProvider`** -- Extends `BaseProvider`. Factory that creates transport instances with type-guard methods (`isNodemailerOptions`, `isMailgunOptions`, `isCustomOptions`) and throws `MailErrorCodes` on invalid configs
47
- - **`MailQueueExecutorProvider`** -- Extends `BaseProvider`. Factory that creates queue executor instances. Throws for missing sub-configs (e.g., `config.internalQueue` or `config.bullmq`)
48
- - **Verification Generators** -- `NumericCodeGenerator`, `RandomTokenGenerator`, `DefaultVerificationDataGenerator`. Generate verification codes, tokens, and data for email verification flows
146
+ **Source:** [`component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/component.ts)
49
147
 
50
- **Tech Stack:**
148
+ ## Binding keys
51
149
 
52
- - **Nodemailer** -- SMTP-based email sending (peer dependency: `nodemailer`)
53
- - **Mailgun** -- Mailgun API client (peer dependency: `mailgun.js`)
54
- - **BullMQ** (optional) -- Redis-backed queue for distributed processing (peer dependency: `bullmq`)
55
- - **Handlebars-style Templates** -- Simple <code v-pre>{{variable}}</code> syntax for email templates (no external dependency)
150
+ | Key | Constant | Type | Required | Default |
151
+ |-----|----------|------|----------|---------|
152
+ | `@app/components/mail/options` | `MailKeys.MAIL_OPTIONS` | `TMailOptions` | **Yes** | -- |
153
+ | `@app/components/mail/queue/executor-config` | `MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG` | `IMailQueueExecutorConfig` | No | `{ type: MailQueueExecutorTypes.DIRECT }` |
154
+ | `@app/components/mail/service` | `MailKeys.MAIL_SERVICE` | `IMailService` | No | `MailService` (singleton) |
155
+ | `@app/components/mail/services/template-engine` | `MailKeys.MAIL_TEMPLATE_ENGINE` | `IMailTemplateEngine` | No | `TemplateEngineService` (singleton) |
156
+ | `@app/components/mail/transport-provider` | `MailKeys.MAIL_TRANSPORT_PROVIDER` | `TGetMailTransportFn` | No | `MailTransportProvider` (singleton) |
157
+ | `@app/components/mail/transport-instance` | `MailKeys.MAIL_TRANSPORT_INSTANCE` | `IMailTransport` | No | Created by the component |
158
+ | `@app/components/mail/queue-executor-provider` | `MailKeys.MAIL_QUEUE_EXECUTOR_PROVIDER` | `TGetMailQueueExecutorFn` | No | `MailQueueExecutorProvider` (singleton) |
159
+ | `@app/components/mail/queue-executor-instance` | `MailKeys.MAIL_QUEUE_EXECUTOR_INSTANCE` | `IMailQueueExecutor` | No | Created by the component |
160
+ | `@app/components/mail/verification/code-generator` | `MailKeys.MAIL_VERIFICATION_CODE_GENERATOR` | `IVerificationCodeGenerator` | No | `NumericCodeGenerator` (transient) |
161
+ | `@app/components/mail/verification/token-generator` | `MailKeys.MAIL_VERIFICATION_TOKEN_GENERATOR` | `IVerificationTokenGenerator` | No | `RandomTokenGenerator` (transient) |
162
+ | `@app/components/mail/verification/data-generator` | `MailKeys.MAIL_VERIFICATION_DATA_GENERATOR` | `IVerificationDataGenerator` | No | `DefaultVerificationDataGenerator` (transient) |
56
163
 
57
- ## Transport Layer
164
+ > [!IMPORTANT]
165
+ > `MailKeys.MAIL_OPTIONS` is the only binding `MailComponent` requires. It throws `Mail options not configured` in `binding()` if the key is not bound. `MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG` is read with `isOptional: true` -- when it is not bound, `createAndBindInstances()` falls back to `{ type: MailQueueExecutorTypes.DIRECT }` rather than failing startup.
58
166
 
59
- ### Transport Layer Implementation
167
+ **Source:** [`common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/keys.ts)
60
168
 
61
- The `MailTransportProvider` extends `BaseProvider` and returns a factory function from its `value()` method. It creates the appropriate transport based on the `provider` field in `MailKeys.MAIL_OPTIONS`:
169
+ ## Configuration
62
170
 
63
- - **`'nodemailer'`** -- Creates a `NodemailerTransportHelper` backed by `nodemailer` with SMTP or OAuth2 auth
64
- - **`'mailgun'`** -- Creates a `MailgunTransportHelper` using the Mailgun HTTP API
65
- - **`'custom'`** -- Expects the `config` value to implement `IMailTransport` directly (must have `send()` and `verify()`)
66
- - **Any other string** -- Falls through to `default` and throws `Unsupported mail provider: <provider>` with `MailErrorCodes.INVALID_CONFIGURATION`
171
+ ### Transport options (`TMailOptions`)
67
172
 
68
- The provider uses three private type-guard methods to narrow the union type before creating transports:
173
+ A discriminated union on `provider`, extending `IBaseMailOptions`:
69
174
 
70
175
  ```typescript
71
- private isNodemailerOptions(options: TMailOptions): options is INodemailerMailOptions
72
- private isMailgunOptions(options: TMailOptions): options is IMailgunMailOptions
73
- private isCustomOptions(options: TMailOptions): options is ICustomMailOptions
176
+ interface IBaseMailOptions {
177
+ from?: string;
178
+ fromName?: string;
179
+ }
180
+
181
+ interface INodemailerMailOptions extends IBaseMailOptions {
182
+ provider: 'nodemailer';
183
+ config: TNodemailerConfig; // SMTPTransport | SMTPTransport.Options | string
184
+ }
185
+
186
+ interface IMailgunMailOptions extends IBaseMailOptions {
187
+ provider: 'mailgun';
188
+ config: TMailgunConfig; // AnyType & { domain: string } -- also requires username, key at runtime
189
+ }
190
+
191
+ interface ICustomMailOptions extends IBaseMailOptions {
192
+ provider: 'custom';
193
+ config: IMailTransport; // Must implement send() and verify()
194
+ }
195
+
196
+ interface IGenericMailOptions extends IBaseMailOptions {
197
+ provider: string;
198
+ config: Record<string, AnyType>;
199
+ }
200
+
201
+ type TMailOptions =
202
+ | INodemailerMailOptions
203
+ | IMailgunMailOptions
204
+ | ICustomMailOptions
205
+ | IGenericMailOptions;
74
206
  ```
75
207
 
76
- For custom transports, an additional `isMailTransport()` utility validates that the config object has `send()` and `verify()` methods, reporting specific missing methods in the error message.
208
+ **Nodemailer (SMTP with basic auth):**
77
209
 
78
- Both built-in transports implement `IMailTransport`:
210
+ ```typescript
211
+ {
212
+ provider: MailProviders.NODEMAILER,
213
+ from: 'noreply@example.com',
214
+ fromName: 'Example App',
215
+ config: {
216
+ host: 'smtp.gmail.com',
217
+ port: 465,
218
+ secure: true,
219
+ auth: { user: 'your-email@gmail.com', pass: 'your-app-password' },
220
+ },
221
+ }
222
+ ```
223
+
224
+ **Nodemailer (OAuth2):**
79
225
 
80
226
  ```typescript
81
- interface IMailTransport {
82
- send(message: IMailMessage): Promise<IMailSendResult>;
83
- verify(): Promise<boolean>;
84
- close?(): Promise<void>;
227
+ {
228
+ provider: MailProviders.NODEMAILER,
229
+ from: applicationEnvironment.get<string>('APP_ENV_MAIL_FROM') ?? 'noreply@example.com',
230
+ fromName: applicationEnvironment.get<string>('APP_ENV_MAIL_FROM_NAME') ?? 'Example App',
231
+ config: {
232
+ host: applicationEnvironment.get<string>('APP_ENV_MAIL_HOST') ?? 'smtp.gmail.com',
233
+ port: +(applicationEnvironment.get<number>('APP_ENV_MAIL_PORT') ?? 465),
234
+ secure: toBoolean(applicationEnvironment.get<boolean>('APP_ENV_MAIL_SECURE') ?? true),
235
+ auth: {
236
+ type: 'oauth2',
237
+ user: applicationEnvironment.get<string>('APP_ENV_MAIL_USER'),
238
+ clientId: applicationEnvironment.get<string>('APP_ENV_MAIL_CLIENT_ID'),
239
+ clientSecret: applicationEnvironment.get<string>('APP_ENV_MAIL_CLIENT_SECRET'),
240
+ refreshToken: applicationEnvironment.get<string>('APP_ENV_MAIL_REFRESH_TOKEN'),
241
+ },
242
+ },
243
+ }
244
+ ```
245
+
246
+ `APP_ENV_MAIL_*` is an app-level convention, not a framework-defined env var -- `applicationEnvironment.get()` reads whatever variables your wrapper component chooses to look up. A matching `.env`:
247
+
248
+ ```
249
+ APP_ENV_MAIL_HOST=smtp.gmail.com
250
+ APP_ENV_MAIL_PORT=465
251
+ APP_ENV_MAIL_SECURE=true
252
+ APP_ENV_MAIL_USER=your-email@gmail.com
253
+ APP_ENV_MAIL_CLIENT_ID=your-oauth2-client-id
254
+ APP_ENV_MAIL_CLIENT_SECRET=your-oauth2-client-secret
255
+ APP_ENV_MAIL_REFRESH_TOKEN=your-oauth2-refresh-token
256
+ ```
257
+
258
+ > [!TIP]
259
+ > For Gmail OAuth2, follow [Google's OAuth2 setup guide](https://developers.google.com/gmail/api/auth/web-server) to obtain the client ID, secret, and refresh token.
260
+
261
+ **Mailgun:**
262
+
263
+ ```typescript
264
+ {
265
+ provider: MailProviders.MAILGUN,
266
+ from: 'noreply@example.com',
267
+ fromName: 'Example App',
268
+ config: {
269
+ username: 'api', // required -- mailgun.js client username
270
+ key: process.env.MAILGUN_API_KEY, // required -- Mailgun API key
271
+ domain: 'mg.example.com', // required
272
+ host: 'api.eu.mailgun.net', // optional -- EU region
273
+ },
274
+ }
275
+ ```
276
+
277
+ > [!IMPORTANT]
278
+ > `MailgunTransportHelper` validates `username`, `key`, and `domain` on construction and throws `Invalid Mailgun configuration | Missing required keys: <keys>` if any is missing -- this happens even though `TMailgunConfig`'s only *typed* requirement is `domain`. `username` and `key` are checked at runtime, not by the type.
279
+
280
+ **Custom transport:**
281
+
282
+ ```typescript
283
+ {
284
+ provider: MailProviders.CUSTOM,
285
+ from: 'noreply@example.com',
286
+ config: myTransportImplementingIMailTransport, // must have send() and verify()
287
+ }
288
+ ```
289
+
290
+ **Generic provider (extensibility escape hatch):**
291
+
292
+ ```typescript
293
+ {
294
+ provider: 'sendgrid',
295
+ from: 'noreply@example.com',
296
+ config: { apiKey: process.env.SENDGRID_API_KEY },
85
297
  }
86
298
  ```
87
299
 
88
- ### Module Validation
300
+ > [!WARNING]
301
+ > `IGenericMailOptions` falls through to the `default` case in `MailTransportProvider` and throws `Unsupported mail provider: <provider>` -- it exists only for you to bind a **custom** `MailTransportProvider` that recognizes the provider string. It is not handled by the framework's built-in provider.
89
302
 
90
- Both `NodemailerTransportHelper` and `MailgunTransportHelper` call `validateModule()` in their `configure()` method before importing the peer dependency. This checks that the required npm module is installed:
303
+ ### Queue executor options (`IMailQueueExecutorConfig`)
91
304
 
92
- - `NodemailerTransportHelper` requires `nodemailer`
93
- - `MailgunTransportHelper` requires `mailgun.js`
305
+ ```typescript
306
+ interface IMailQueueExecutorConfig {
307
+ type: TConstValue<typeof MailQueueExecutorTypes>; // 'direct' | 'internal-queue' | 'bullmq'
308
+ internalQueue?: {
309
+ identifier: string;
310
+ };
311
+ bullmq?: {
312
+ redis: IRedisSingleHelperOptions;
313
+ queue: { identifier: string; name: string };
314
+ mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED -- no default
315
+ };
316
+ }
317
+ ```
94
318
 
95
- If the module is not installed, `validateModule()` throws an error identifying the missing peer dependency.
319
+ ```typescript
320
+ // Direct (also the implicit default when MAIL_QUEUE_EXECUTOR_CONFIG is unbound)
321
+ { type: 'direct' }
322
+
323
+ // Internal queue (in-memory)
324
+ { type: 'internal-queue', internalQueue: { identifier: 'mail-internal-queue' } }
325
+
326
+ // BullMQ (Redis-backed)
327
+ {
328
+ type: 'bullmq',
329
+ bullmq: {
330
+ redis: { host: 'localhost', port: 6379, password: 'your-redis-password' },
331
+ queue: { identifier: 'mail-queue', name: 'mail-queue' },
332
+ mode: 'both', // 'queue-only' | 'worker-only' | 'both' -- required, no default
333
+ },
334
+ }
335
+ ```
96
336
 
97
- **Nodemailer Transport:**
337
+ > [!NOTE]
338
+ > Choosing an executor: **`direct`** for development or low-volume apps (no queueing overhead); **`internal-queue`** for single-instance apps with moderate volume (in-memory, with retry); **`bullmq`** for distributed or high-volume systems (Redis-backed, configurable concurrency/priority/backoff).
98
339
 
99
- The `NodemailerTransportHelper` extends `BaseHelper` and wraps Nodemailer's SMTP transport. In `configure()`, it calls `require('nodemailer')` and creates a transporter. Key behaviors:
100
- - `send()` maps `IMailMessage` fields to Nodemailer's mail options, joining array recipients with `, `
101
- - `send()` catches transport errors and returns `{ success: false, error: ... }` instead of throwing
102
- - `verify()` delegates to Nodemailer's built-in `transporter.verify()` SMTP handshake
103
- - `close()` calls `transporter.close()` to release the connection
340
+ **Source:** [`common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/types.ts)
104
341
 
105
- **Mailgun Transport:**
342
+ ### Constants
106
343
 
107
- The `MailgunTransportHelper` extends `BaseHelper` and uses the Mailgun REST API via `mailgun.js`. In `configure()`, it creates a `Mailgun` client using `FormData`. Key behaviors:
108
- - `send()` converts `IMailMessage` to Mailgun's format: `to` becomes an array, `replyTo` becomes `h:Reply-To`, all custom headers are prefixed with `h:`
109
- - Attachments are mapped to `{ filename, data }` objects where `data` is `path ?? content ?? Buffer.from('')`
110
- - `verify()` sends a test email to `verify@<domain>` with `o:testmode: 'yes'` flag to check API credentials without actually sending
111
- - No `close()` method (HTTP API is stateless)
344
+ | Constant | Value | Description |
345
+ |----------|-------|-------------|
346
+ | `MailDefaults.BATCH_CONCURRENCY` | `5` | Default concurrent sends in `sendBatch()` |
347
+ | `MailDefaults.FALLBACK_FROM` | `'noreply@example.com'` | Used by `getDefaultFrom()` when `options.from` is unset |
348
+ | `MailExecutorErrors.PROCESSOR_NOT_SET` | `'Processor not set. Call setProcessor() first.'` | Thrown by all three queue executors |
349
+ | `MailQueueExecutorTypes.DIRECT` | `'direct'` | Immediate execution |
350
+ | `MailQueueExecutorTypes.INTERNAL_QUEUE` | `'internal-queue'` | In-memory queue |
351
+ | `MailQueueExecutorTypes.BULLMQ` | `'bullmq'` | Redis-backed queue |
352
+ | `BullMQExecutorModes.QUEUE_ONLY` | `'queue-only'` | Producer only (enqueue) |
353
+ | `BullMQExecutorModes.WORKER_ONLY` | `'worker-only'` | Consumer only (process) |
354
+ | `BullMQExecutorModes.BOTH` | `'both'` | Full duplex (produce + consume) |
112
355
 
113
- **Custom Transport:**
356
+ `MailQueueExecutorTypes` and `BullMQExecutorModes` both carry a `SCHEME_SET`/`MODE_SET` and a static `isValid()`:
114
357
 
115
- You can provide your own transport implementation by setting `provider: MailProviders.CUSTOM` and passing an object that implements `IMailTransport` as the `config` value. The provider validates that `send()` and `verify()` are functions, reporting specific missing methods. This is useful for integrating with services like SendGrid, AWS SES, or custom SMTP relays.
358
+ ```typescript
359
+ MailQueueExecutorTypes.isValid('bullmq'); // true
360
+ MailQueueExecutorTypes.isValid('unknown'); // false
361
+ BullMQExecutorModes.isValid('both'); // true
362
+ BullMQExecutorModes.isValid('invalid'); // false
363
+ ```
116
364
 
117
- ## IMailService Interface
365
+ #### `MailErrorCodes`
366
+
367
+ Built through `MessageCode.build()`, so every value is lower-case (`ApplicationError` lower-cases whatever it is handed):
368
+
369
+ | Constant | Value | Description |
370
+ |----------|-------|-------------|
371
+ | `MailErrorCodes.INVALID_CONFIGURATION` | `'core.mail.invalid_configuration'` | Invalid or missing configuration (transport, template engine, subject, body) |
372
+ | `MailErrorCodes.SEND_FAILED` | `'core.mail.send_failed'` | Single email send failed |
373
+ | `MailErrorCodes.VERIFICATION_FAILED` | `'core.mail.verification_failed'` | Transport connection verification failed |
374
+ | `MailErrorCodes.INVALID_RECIPIENT` | `'core.mail.invalid_recipient'` | Missing or empty recipient address |
375
+ | `MailErrorCodes.BATCH_SEND_FAILED` | `'core.mail.batch_send_failed'` | Batch email operation failed |
376
+ | `MailErrorCodes.TEMPLATE_NOT_FOUND` | `'core.mail.template_not_found'` | Template name not found in registry |
377
+
378
+ **Source:** [`common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/constants.ts)
379
+
380
+ ## `IMailService` interface
118
381
 
119
382
  ```typescript
120
383
  interface IMailService {
121
- // Send a single email
122
384
  send(message: IMailMessage): Promise<IMailSendResult>;
123
385
 
124
- // Send multiple emails with controlled concurrency
125
386
  sendBatch(
126
387
  messages: IMailMessage[],
127
388
  options?: { concurrency?: number },
128
389
  ): Promise<IMailSendResult[]>;
129
390
 
130
- // Send email using a registered template
131
391
  sendTemplate(opts: {
132
392
  templateName: string;
133
393
  data: Record<string, any>;
@@ -135,100 +395,77 @@ interface IMailService {
135
395
  options?: Partial<IMailMessage>;
136
396
  }): Promise<IMailSendResult>;
137
397
 
138
- // Verify transport connection
139
398
  verify(): Promise<boolean>;
140
399
  }
141
400
  ```
142
401
 
143
- **Method Details:**
144
-
145
- **`send(message: IMailMessage)`**
146
-
147
- Sends a single email using the configured transport. Internally:
148
- 1. Calls `validateMessage()` which throws for missing `to`, `subject`, or both `text`/`html` (see error reference below)
149
- 2. Merges `message.from` with the default from address (via `getDefaultFrom()`)
150
- 3. Delegates to `transport.send()`
151
- 4. Returns a result object with `success`, `messageId`, and optional `error` fields
152
- 5. If the transport throws, catches the error and re-throws with `MailErrorCodes.SEND_FAILED`
402
+ **`send(message)`**
153
403
 
154
- **`sendBatch(messages: IMailMessage[], options?: { concurrency?: number })`**
404
+ 1. `validateMessage()` throws for missing `to`, missing `subject`, or missing both `text`/`html` (400, see below).
405
+ 2. Merges `message.from` with `getDefaultFrom()` if unset.
406
+ 3. Delegates to `transport.send()`.
407
+ 4. Returns `{ success, messageId, error? }`.
408
+ 5. If the transport throws and the error is **not** already an `ApplicationError`, it is caught and re-thrown as `MailErrorCodes.SEND_FAILED` (500). An `ApplicationError` thrown earlier in the same `try` block (e.g. from `validateMessage()`) is re-thrown as-is, unchanged.
155
409
 
156
- Sends multiple emails with controlled concurrency using `executePromiseWithLimit()`. Default concurrency is `MailDefaults.BATCH_CONCURRENCY` (5). Each message is sent via `send()` individually. If an individual `send()` throws, it is caught and converted to `{ success: false, error: '...' }` so the batch continues. If the entire batch operation fails, throws with `MailErrorCodes.BATCH_SEND_FAILED`.
410
+ **`sendBatch(messages, options?)`**
157
411
 
158
- **`sendTemplate(opts: { templateName, data, recipients, options? })`**
412
+ Sends every message via `send()` with concurrency bounded by `executePromiseWithLimit()`, default `MailDefaults.BATCH_CONCURRENCY` (`5`). A `send()` that throws is caught per-message and downgraded to `{ success: false, error }`; a failure in the batch operation itself throws `MailErrorCodes.BATCH_SEND_FAILED`.
159
413
 
160
- Renders a registered template with the provided data and sends the email. The subject is resolved through a priority chain:
414
+ **`sendTemplate(opts)`**
161
415
 
162
- 1. `options.subject` -- explicit override from the caller
163
- 2. Template subject rendered through the template engine (if `templateData.subject` is defined)
164
- 3. `'No Subject'` -- fallback if neither is provided
165
-
166
- Throws `MailErrorCodes.INVALID_CONFIGURATION` if the template engine is not configured. Re-throws any other errors (including `TEMPLATE_NOT_FOUND` from the template engine).
416
+ Renders a registered template and sends it. Subject resolution order: `options.subject` -> the template's own `subject` (rendered through the same engine) -> `'No Subject'`. Throws `MailErrorCodes.INVALID_CONFIGURATION` ("Template engine not configured") if `templateEngine` was not injected. Re-throws any other error unchanged, including `TEMPLATE_NOT_FOUND` from the template engine.
167
417
 
168
418
  **`verify()`**
169
419
 
170
- Verifies the transport connection without sending an email. Delegates to `transport.verify()`. If the transport throws, catches and re-throws with `MailErrorCodes.VERIFICATION_FAILED`.
171
-
172
- ### Protected Methods (MailService)
420
+ Delegates to `transport.verify()`. If the transport throws, wraps it as `MailErrorCodes.VERIFICATION_FAILED` (500).
173
421
 
174
- **`validateMessage(message: IMailMessage)`**
422
+ ### Protected methods (`MailService`)
175
423
 
176
- Pre-transport validation that throws immediately for invalid messages:
424
+ **`validateMessage(message)`**
177
425
 
178
- | Check | Error Code | Status | Message |
426
+ | Check | Error code | Status | Message |
179
427
  |-------|-----------|--------|---------|
180
- | `to` is falsy or empty array | `INVALID_RECIPIENT` | 400 | `Recipient email address is required` |
428
+ | `to` is falsy or an empty array | `INVALID_RECIPIENT` | 400 | `Recipient email address is required` |
181
429
  | `subject` is falsy | `INVALID_CONFIGURATION` | 400 | `Email subject is required` |
182
430
  | Both `text` and `html` are falsy | `INVALID_CONFIGURATION` | 400 | `Email must have either text or html content` |
183
431
 
184
432
  **`getDefaultFrom()`**
185
433
 
186
- Constructs the default "from" address. If `options.fromName` is set, returns `"fromName" <from>`. Otherwise returns `options.from ?? 'noreply@example.com'`.
434
+ - If `options.from` is unset, returns `MailDefaults.FALLBACK_FROM` (`'noreply@example.com'`).
435
+ - Else if `options.fromName` is unset, returns `options.from` as-is.
436
+ - Else returns `"${fromName}" <${from}>`.
437
+
438
+ **Source:** [`services/mail.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/services/mail.service.ts)
187
439
 
188
- ## IMailMessage Interface
440
+ ## `IMailMessage` interface
189
441
 
190
442
  ```typescript
191
443
  interface IMailMessage {
192
- from?: string; // Sender email (uses default if not provided)
193
- to: string | string[]; // Recipient(s)
194
- cc?: string | string[]; // CC recipient(s)
195
- bcc?: string | string[]; // BCC recipient(s)
196
- replyTo?: string; // Reply-to address
197
- subject: string; // Email subject
198
- text?: string; // Plain text content
199
- html?: string; // HTML content
200
- attachments?: IMailAttachment[]; // File attachments
201
- headers?: Record<string, string>; // Custom headers
202
- requireValidate?: boolean; // Validate template data
203
- [key: string]: any; // Additional arbitrary fields
444
+ from?: string; // Uses getDefaultFrom() if not provided
445
+ to: string | string[];
446
+ cc?: string | string[];
447
+ bcc?: string | string[];
448
+ replyTo?: string;
449
+ subject: string;
450
+ text?: string;
451
+ html?: string;
452
+ attachments?: IMailAttachment[];
453
+ headers?: Record<string, string>;
454
+ requireValidate?: boolean; // Passed through to sendTemplate()'s render() call
455
+ [key: string]: any; // Open-ended -- provider-specific fields pass through
204
456
  }
205
457
  ```
206
458
 
207
- **Field Details:**
208
-
209
- **`from`**
210
-
211
- Sender email address. If not provided, uses the default `from` value from `MailKeys.MAIL_OPTIONS`. When `fromName` is configured, the default from is formatted as `"Display Name" <email@example.com>`. Can be overridden per-message for multi-tenant scenarios.
212
-
213
- **`to`, `cc`, `bcc`**
214
-
215
- Recipient addresses. Can be a single string or an array of strings. Format can be either plain email (`user@example.com`) or display name + email (`John Doe <john@example.com>`). For Nodemailer, arrays are joined with `, `. For Mailgun, arrays are passed as-is.
216
-
217
- **`replyTo`**
218
-
219
- Reply-to address if different from the sender. Useful for no-reply addresses that route replies to a support inbox. Mailgun maps this to `h:Reply-To`.
220
-
221
- **`subject`**
222
-
223
- Email subject line. Supports template variables when used with `sendTemplate()` (the subject is rendered through the same template engine).
224
-
225
- **`text`, `html`**
226
-
227
- Plain text and HTML versions of the email body. At least one must be provided (validated by `validateMessage()`). Most email clients prefer HTML but fall back to text if HTML is not available. Best practice is to provide both.
228
-
229
- **`attachments`**
230
-
231
- Array of `IMailAttachment` objects:
459
+ | Field | Notes |
460
+ |-------|-------|
461
+ | `from` | Falls back to `getDefaultFrom()`. Can be overridden per message (multi-tenant scenarios). |
462
+ | `to`, `cc`, `bcc` | Single string or array. Nodemailer joins arrays with `', '`; Mailgun passes `to` as-is (always coerced to an array), `cc`/`bcc` pass through unmodified. |
463
+ | `replyTo` | Nodemailer passes it directly; Mailgun maps it to `h:Reply-To`. |
464
+ | `subject` | Rendered through the template engine when set via `sendTemplate()`. |
465
+ | `text`, `html` | At least one is required (`validateMessage()`). |
466
+ | `attachments` | See `IMailAttachment` below. |
467
+ | `headers` | Nodemailer passes them directly; Mailgun prefixes every key with `h:`. |
468
+ | `requireValidate` | `true` makes template rendering throw on missing placeholders instead of preserving them as literal text. Defaults to `false`. |
232
469
 
233
470
  ```typescript
234
471
  interface IMailAttachment {
@@ -241,28 +478,17 @@ interface IMailAttachment {
241
478
  }
242
479
  ```
243
480
 
244
- Examples:
245
- - A file path: `{ filename: 'doc.pdf', path: '/path/to/doc.pdf' }`
246
- - A buffer: `{ filename: 'data.txt', content: Buffer.from('...') }`
247
- - An inline image: `{ filename: 'logo.png', path: '...', cid: 'logo' }`
248
-
249
- **`headers`**
250
-
251
- Custom SMTP headers. For Nodemailer, passed directly. For Mailgun, each key is auto-prefixed with `h:`.
481
+ - File path: `{ filename: 'doc.pdf', path: '/path/to/doc.pdf' }`
482
+ - Buffer: `{ filename: 'data.txt', content: Buffer.from('...') }`
483
+ - Inline image: `{ filename: 'logo.png', path: '...', cid: 'logo' }`
484
+ - Mailgun maps each attachment to `{ filename, data: att.path ?? att.content ?? Buffer.from('') }`.
252
485
 
253
- **`requireValidate`**
486
+ **Source:** [`common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/types.ts)
254
487
 
255
- When `true`, template rendering will throw an error if any <code v-pre>{{variable}}</code> placeholders are missing from the data object. Defaults to `false` (missing variables are preserved as their original placeholder text, not replaced with empty strings).
256
-
257
- **`[key: string]: any`**
258
-
259
- The interface is open-ended -- additional fields are accepted for provider-specific options.
260
-
261
- ## IMailTemplateEngine Interface
488
+ ## `IMailTemplateEngine` interface
262
489
 
263
490
  ```typescript
264
491
  interface IMailTemplateEngine {
265
- // Render a template with data
266
492
  render(opts: {
267
493
  templateData?: string;
268
494
  templateName?: string;
@@ -270,144 +496,173 @@ interface IMailTemplateEngine {
270
496
  requireValidate?: boolean;
271
497
  }): string;
272
498
 
273
- // Register a new template
274
499
  registerTemplate(opts: { name: string; content: string }): void;
275
500
 
276
- // Validate template data
277
501
  validateTemplateData(opts: { template: string; data: Record<string, any> }): {
278
502
  isValid: boolean;
279
503
  missingKeys: string[];
280
504
  allKeys: string[];
281
505
  };
282
506
 
283
- // Get a registered template
284
507
  getTemplate(name: string): ITemplate | undefined;
285
-
286
- // List all registered templates
287
508
  listTemplates(): ITemplate[];
288
-
289
- // Check if template exists
290
509
  hasTemplate(name: string): boolean;
291
-
292
- // Remove a template
293
510
  removeTemplate(name: string): boolean;
294
511
  }
295
512
  ```
296
513
 
297
- **Method Details:**
514
+ `TemplateEngineService` also exposes `clearTemplates(): void` -- it resets the registry but is **not** part of the `IMailTemplateEngine` interface, so it is only reachable when you hold the concrete class, not the interface type.
298
515
 
299
- **`render(opts: { templateData?, templateName?, data, requireValidate? })`**
516
+ | Method | Behavior |
517
+ |--------|----------|
518
+ | `render(opts)` | Renders by `templateName` (registry lookup) or raw `templateData`. Throws if neither is given. Throws `TEMPLATE_NOT_FOUND` (404) if `templateName` is not registered. Delegates to `renderSimpleTemplate()`. |
519
+ | `registerTemplate(opts)` | `options` (on the class) accepts `subject`/`description` (`Partial<ITemplate>`). Overwrites an existing template with the same name. |
520
+ | `validateTemplateData(opts)` | Extracts every unique <code v-pre>{{key}}</code> via `/\{\{(\s*[\w.]+\s*)\}\}/g`, deduplicates, resolves nested dot-notation values. Returns `isValid`, `missingKeys`, `allKeys`. |
521
+ | `getTemplate(name)` | Returns `undefined` if not registered. |
522
+ | `listTemplates()` | All templates, as an array of `ITemplate`. |
523
+ | `hasTemplate(name)` | Registry membership check. |
524
+ | `removeTemplate(name)` | Logs the removal, returns `true`/`false`. |
300
525
 
301
- Renders a template by name (from registry) or by raw template string (`templateData`). At least one of `templateData` or `templateName` must be provided -- throws if neither is given. If `templateName` is used, looks up the template and throws `TEMPLATE_NOT_FOUND` if not registered. Delegates to `renderSimpleTemplate()` which replaces all <code v-pre>{{variable}}</code> placeholders with values from the `data` object. If `requireValidate` is `true`, throws `INVALID_CONFIGURATION` if any placeholders are missing from the data.
526
+ ```typescript
527
+ interface ITemplate {
528
+ name: string;
529
+ content?: string;
530
+ render?: (data: Record<string, AnyType>) => string;
531
+ subject?: string;
532
+ description?: string;
533
+ }
534
+ ```
302
535
 
303
- **`registerTemplate(opts: { name, content, options? })`**
536
+ The `render` field supports a custom per-template render function, though the built-in `TemplateEngineService` always uses `content` + `renderSimpleTemplate()` instead.
304
537
 
305
- Registers a new template in the in-memory registry. The `options` parameter (on the class implementation) can include `subject` and `description` via `Partial<ITemplate>`. Overwrites any existing template with the same name.
538
+ **Source:** [`services/template.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/services/template.service.ts)
306
539
 
307
- **`validateTemplateData(opts: { template, data })`**
540
+ ## Transport layer
308
541
 
309
- Extracts all <code v-pre>{{variable}}</code> placeholders from the template string using the regex `/\{\{(\s*[\w.]+\s*)\}\}/g`. Deduplicates keys. For each unique key, resolves nested values via dot notation. Returns:
310
- - `isValid` -- `true` if all placeholders have non-null, non-undefined values
311
- - `missingKeys` -- Array of placeholder names that are missing or null/undefined in the data
312
- - `allKeys` -- Array of all unique placeholder names found in the template
542
+ `MailTransportProvider.value()` returns a factory function that switches on `TMailOptions.provider`:
313
543
 
314
- **`getTemplate(name: string)`**
544
+ | Provider | Result |
545
+ |----------|--------|
546
+ | `'nodemailer'` | `NodemailerTransportHelper`, backed by `nodemailer` |
547
+ | `'mailgun'` | `MailgunTransportHelper`, backed by the Mailgun HTTP API (`mailgun.js`) |
548
+ | `'custom'` | The `config` value itself, validated to implement `IMailTransport` |
549
+ | Any other string | Throws `Unsupported mail provider: <provider>` (`INVALID_CONFIGURATION`, 500) |
315
550
 
316
- Retrieves a registered template by name. Returns `undefined` if the template does not exist.
551
+ Type narrowing uses three private guards, each checking `provider === X && 'config' in options`:
317
552
 
318
- **`listTemplates()`**
553
+ ```typescript
554
+ private isNodemailerOptions(options: TMailOptions): options is INodemailerMailOptions
555
+ private isMailgunOptions(options: TMailOptions): options is IMailgunMailOptions
556
+ private isCustomOptions(options: TMailOptions): options is ICustomMailOptions
557
+ ```
319
558
 
320
- Returns an array of all registered templates (values from the internal `Map`).
559
+ For `custom`, an additional `isMailTransport()` utility check reports specifically which of `send`/`verify` is missing.
321
560
 
322
- **`hasTemplate(name: string)`**
561
+ ```typescript
562
+ interface IMailTransport {
563
+ send(message: IMailMessage): Promise<IMailSendResult>;
564
+ verify(): Promise<boolean>;
565
+ close?(): Promise<void>;
566
+ }
567
+ ```
323
568
 
324
- Checks if a template with the given name exists in the registry.
569
+ ### Peer dependency loading
325
570
 
326
- **`removeTemplate(name: string)`**
571
+ Neither transport calls a shared `validateModule()` helper. `configure()` calls `require('nodemailer')` / `require('mailgun.js')` directly -- if the package is not installed, Node's own `Cannot find module '<name>'` error propagates uncaught from the constructor, not a framework-formatted message.
327
572
 
328
- Removes a template from the registry. Logs the removal. Returns `true` if the template was found and removed, `false` otherwise.
573
+ **Nodemailer (`NodemailerTransportHelper`, extends `BaseHelper`):**
329
574
 
330
- ### ITemplate Interface
575
+ - `configure()` builds the transporter via `nodemailer.createTransport(config)`.
576
+ - `send()` maps `IMailMessage` fields to Nodemailer's mail options, joining array recipients with `', '`. Catches transport errors and returns `{ success: false, error }` -- never throws.
577
+ - `verify()` delegates to `transporter.verify()` (SMTP handshake). Catches errors and returns `false` -- never throws.
578
+ - `close()` calls `transporter.close()`.
331
579
 
332
- ```typescript
333
- interface ITemplate {
334
- name: string;
335
- content?: string;
336
- render?: (data: Record<string, AnyType>) => string;
337
- subject?: string;
338
- description?: string;
339
- }
340
- ```
580
+ **Mailgun (`MailgunTransportHelper`, extends `BaseHelper`):**
581
+
582
+ - `configure()` calls `validateConfig()` **before** building the client: `username`, `key`, and `domain` must all be present in `config`, or it throws `Invalid Mailgun configuration | Missing required keys: <keys>` (`INVALID_CONFIGURATION`, 500). This check runs even though `TMailgunConfig`'s type only requires `domain`.
583
+ - `send()` converts `IMailMessage` to Mailgun's format: `to` is coerced to an array, `replyTo` becomes `h:Reply-To`, every custom header is prefixed `h:`. Attachments map to `{ filename, data: path ?? content ?? Buffer.from('') }`. Catches send errors and returns `{ success: false, error }` -- never throws.
584
+ - `verify()` sends a test message to `verify@<domain>` with `o:testmode: 'yes'` (Mailgun has no dedicated verify endpoint). Catches errors and returns `false` -- never throws.
585
+ - No `close()` -- the HTTP API is stateless.
341
586
 
342
- The `render` function on the interface supports custom render implementations, though the built-in `TemplateEngineService` uses `content` + `renderSimpleTemplate()` instead.
587
+ **Custom transport:** set `provider: MailProviders.CUSTOM` and pass an object implementing `IMailTransport` as `config`. Useful for SendGrid, AWS SES, or a custom SMTP relay that the framework does not ship a helper for.
343
588
 
344
- ## Additional Interfaces
589
+ **Source:** [`helpers/transporters/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/transporters)
345
590
 
346
- ### IMailSendResult
591
+ ## Queue executor implementations
592
+
593
+ All three implement:
347
594
 
348
595
  ```typescript
349
- interface IMailSendResult {
350
- success: boolean;
351
- messageId?: string;
352
- response?: any;
353
- error?: string;
596
+ interface IMailQueueExecutor {
597
+ enqueueVerificationEmail(email: string, options?: IMailQueueOptions): Promise<IMailQueueResult>;
598
+ setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void;
354
599
  }
355
600
  ```
356
601
 
357
- Returned by `send()` and individual entries in the `sendBatch()` result array. Transport-level errors populate `error` with the message string.
602
+ **`DirectMailExecutorHelper`** (extends `BaseHelper`) -- calls the processor immediately, no queue. Returns `{ queued: false, message: 'Email sent immediately (no queue)', result }`. Throws `MailExecutorErrors.PROCESSOR_NOT_SET` if `enqueueVerificationEmail()` runs before `setProcessor()`.
358
603
 
359
- ### IMailQueueExecutor
604
+ **`InternalQueueMailExecutorHelper`** (extends `BaseHelper`, constructor takes `IInternalQueueMailExecutorOpts`) -- wraps `SequentialQueueHelper<IQueueJobPayload>` from `@venizia/ignis-helpers`, `autoDispatch: true`.
605
+
606
+ - Job IDs: `job_<counter>_<timestamp>`.
607
+ - `options.delay > 0` schedules the enqueue itself via `setTimeout`, tracked in an internal `delayedJobs: Map<string, NodeJS.Timeout>`.
608
+ - Retry: on a thrown error or `{ success: false }` from the processor, retries up to `options.attempts ?? 3` times. `calculateBackoff()`: no `backoff` config -> `1000ms` fixed; `type: 'fixed'` -> the raw `delay`; `type: 'exponential'` -> `delay * 2 ** (attempt - 1)`.
609
+ - `close()` clears every pending delayed/retry `setTimeout` -- without it, a live timer keeps the event loop open past shutdown.
610
+ - No persistence across restarts.
611
+
612
+ **`BullMQMailExecutorHelper`** (extends `BaseHelper`, constructor takes `IBullMQMailExecutorOpts`) -- wraps `BullMQHelper` (`@venizia/ignis-helpers/bullmq`) and a `RedisSingleHelper` connection.
613
+
614
+ | Mode | Queue created | Workers created | Can enqueue | Can process |
615
+ |------|----------------|------------------|-------------|-------------|
616
+ | `'queue-only'` | Yes | No (`setProcessor()` skips worker creation, logs a warning) | Yes -- processor not required | No |
617
+ | `'worker-only'` | No | Yes | No (throws) | Yes |
618
+ | `'both'` | Yes | Yes | Yes -- processor required | Yes |
619
+
620
+ - `setProcessor(processor, opts?)` is `async`. It clears all existing workers, then creates `opts?.numberOfWorkers ?? 1` workers, each with `concurrencyPerWorker ?? 5` and `lockDuration ?? 30000`ms. In `queue-only` mode it stores the processor and returns without creating any worker.
621
+ - `enqueueVerificationEmail()` throws `Cannot enqueue jobs in worker-only mode...` in `worker-only` mode; throws `Queue helper not initialized...` if the queue was never created (should not happen in a queue-enabled mode); throws `PROCESSOR_NOT_SET` only when `mode !== 'queue-only'` and no processor is set. Enqueue options: `attempts: options?.attempts ?? 3`, `backoff: { type: options?.backoff?.type ?? 'exponential', delay: options?.backoff?.delay ?? 1000 }`, `removeOnComplete: true`, `removeOnFail: false`.
622
+ - **Dynamic workers:** `addWorker(opts)` (requires a processor, unique identifier, default concurrency `5`, default lock duration `30000`ms), `removeWorker(index)` (closes then splices, returns `false` if out of range), `clearWorkers()` (closes and empties, called internally by `setProcessor()`), `getWorkerCount()`, `getMode()`.
623
+ - `close()` tears down workers, then the queue, then the Redis connection -- every step runs even if an earlier one failed, and all failures are collected into one thrown error at the end.
360
624
 
361
625
  ```typescript
362
- interface IMailQueueExecutor {
363
- enqueueVerificationEmail(email: string, options?: IMailQueueOptions): Promise<IMailQueueResult>;
364
- setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void;
626
+ interface IBullMQMailExecutorOpts {
627
+ redis: IRedisSingleHelperOptions; // from @venizia/ignis-helpers
628
+ queue: { identifier: string; name: string };
629
+ mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED, no default
365
630
  }
366
631
  ```
367
632
 
368
- ### IMailQueueOptions
633
+ **Source:** [`helpers/executors/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/helpers/executors)
634
+
635
+ ## Additional interfaces
369
636
 
370
637
  ```typescript
638
+ interface IMailSendResult {
639
+ success: boolean;
640
+ messageId?: string;
641
+ response?: any;
642
+ error?: string;
643
+ }
644
+
371
645
  interface IMailQueueOptions {
372
646
  priority?: number;
373
647
  delay?: number;
374
648
  attempts?: number;
375
- backoff?: {
376
- type: 'fixed' | 'exponential';
377
- delay: number;
378
- };
649
+ backoff?: { type: 'fixed' | 'exponential'; delay: number };
379
650
  }
380
- ```
381
-
382
- ### IMailQueueResult
383
651
 
384
- ```typescript
385
652
  interface IMailQueueResult {
386
653
  jobId?: string;
387
- queued: boolean;
654
+ queued: boolean; // false for direct execution, true for internal-queue and bullmq
388
655
  message: string;
389
- result?: IMailProcessorResult;
656
+ result?: IMailProcessorResult; // populated only for direct execution (synchronous processor)
390
657
  }
391
- ```
392
-
393
- Returned by `enqueueVerificationEmail()`. The `queued` field is `false` for direct execution, `true` for internal queue and BullMQ. The `result` field is populated only for direct execution (since the processor runs synchronously).
394
658
 
395
- ### IMailProcessorResult
396
-
397
- ```typescript
398
659
  interface IMailProcessorResult {
399
660
  success: boolean;
400
661
  message: string;
401
662
  expiresInMinutes: number;
402
663
  nextResendAt?: string;
403
664
  }
404
- ```
405
665
 
406
- The return type of the processor function registered via `setProcessor()`.
407
-
408
- ### IVerificationData
409
-
410
- ```typescript
411
666
  interface IVerificationData {
412
667
  verificationCode: string;
413
668
  codeGeneratedAt: string;
@@ -418,88 +673,50 @@ interface IVerificationData {
418
673
  tokenExpiresAt: string;
419
674
  lastCodeSentAt: string;
420
675
  }
421
- ```
422
-
423
- ### IVerificationGenerationOptions
424
676
 
425
- ```typescript
426
677
  interface IVerificationGenerationOptions {
427
- codeLength: number;
678
+ codeLength: number; // all four fields required, no defaults
428
679
  tokenBytes: number;
429
680
  codeExpiryMinutes: number;
430
681
  tokenExpiryHours: number;
431
682
  }
432
- ```
433
-
434
- All fields are required. Passed to `DefaultVerificationDataGenerator.generateVerificationData()`.
435
683
 
436
- ### IBullMQMailExecutorOpts
437
-
438
- ```typescript
439
- interface IBullMQMailExecutorOpts {
440
- redis: IRedisHelperOptions;
441
- queue: { identifier: string; name: string };
442
- mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED
684
+ interface IQueueJobPayload {
685
+ id: string;
686
+ email: string;
687
+ options?: IMailQueueOptions;
688
+ attempts: number;
689
+ scheduledAt: number;
443
690
  }
444
- ```
445
-
446
- The `mode` field is **required** -- there is no default value. This is the config object for `IMailQueueExecutorConfig.bullmq`.
447
-
448
- ### BullMQ Dynamic Worker Management Methods
449
-
450
- Dynamic worker management methods (on `BullMQMailExecutorHelper`, not on the interface):
451
-
452
- - **`addWorker(opts)`** -- Adds a new BullMQ worker with configurable concurrency (default 5) and lock duration (default 30000ms). Requires `setProcessor()` to have been called first. Each worker gets a unique identifier
453
- - **`removeWorker(index)`** -- Removes a worker by its array index. Calls `worker.close()` before removal. Returns `false` if the index is out of range
454
- - **`clearWorkers()`** -- Closes all workers and empties the worker array. Called internally by `setProcessor()` before creating new workers
455
- - **`getWorkerCount()`** -- Returns the current number of active workers
456
- - **`getMode()`** -- Returns the current executor mode
457
-
458
- **Extended `setProcessor()` signature (BullMQ only):**
459
-
460
- Unlike the interface's synchronous `setProcessor()`, the BullMQ executor's version is `async` and accepts an optional second argument:
461
691
 
462
- ```typescript
463
- async setProcessor(
464
- processor: (email: string) => Promise<IMailProcessorResult>,
465
- opts?: {
466
- numberOfWorkers?: number; // default: 1
467
- concurrencyPerWorker?: number; // default: 5
468
- lockDuration?: number; // default: 30000 (ms)
469
- },
470
- ): Promise<void>
692
+ interface IInternalQueueMailExecutorOpts {
693
+ identifier: string;
694
+ }
471
695
  ```
472
696
 
473
- It clears all existing workers before creating new ones. In `queue-only` mode, it stores the processor but skips worker creation entirely (logs a warning).
697
+ **Source:** [`common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/common/types.ts)
474
698
 
475
- ## Utility Functions
476
-
477
- ### Type Utilities
699
+ ## Utility functions
478
700
 
479
701
  ```typescript
480
- // Check if a value implements IMailTransport
702
+ // type.utility.ts
481
703
  function isMailTransport(value: AnyType): value is IMailTransport;
482
-
483
- // Check if a value is valid TMailOptions
484
704
  function isValidMailOptions(options: AnyType): options is TMailOptions;
485
- ```
486
705
 
487
- `isMailTransport()` checks for `send` and `verify` as functions, and `close` as either a function or undefined.
488
-
489
- `isValidMailOptions()` checks for a string `provider` field and a truthy `config` field.
706
+ // verification.utility.ts
707
+ function getExpiryTime(minutes: number): Date; // Date `minutes` minutes from now
708
+ function getExpiryTimeInHours(hours: number): Date; // Date `hours` hours from now
709
+ ```
490
710
 
491
- ### Verification Utilities
711
+ - `isMailTransport()` checks that `send` and `verify` are functions, and that `close` is either a function or `undefined`.
712
+ - `isValidMailOptions()` checks that `provider` is a string and `config` is truthy -- it does not validate the shape of `config` against the specific provider.
492
713
 
493
- ```typescript
494
- // Get a Date object `minutes` minutes in the future
495
- function getExpiryTime(minutes: number): Date;
496
-
497
- // Get a Date object `hours` hours in the future
498
- function getExpiryTimeInHours(hours: number): Date;
499
- ```
714
+ **Source:** [`utilities/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/mail/utilities)
500
715
 
501
- ## See Also
716
+ ## See also
502
717
 
503
- - [Setup & Configuration](./) -- Quick reference, setup steps, configuration options, and binding keys
504
- - [Usage & Examples](./usage) -- Sending emails, templates, queue executors, and verification
505
- - [Error Reference](./errors) -- Error codes and troubleshooting
718
+ - [Overview](./) -- quick start, imports, common configuration tasks
719
+ - [Usage & Examples](./usage) -- sending emails, templates, queue executors, verification generators
720
+ - [Error Reference](./errors) -- error codes and troubleshooting
721
+ - [Queue Helper](/extensions/helpers/queue/) -- `SequentialQueueHelper` and `BullMQHelper`
722
+ - [Redis Helper](/extensions/helpers/redis/) -- `RedisSingleHelper`, `IRedisSingleHelperOptions`