Mail Component Reference
Every binding key, configuration variant, interface, and internal mechanism of MailComponent. For the task-oriented walkthrough, see Usage & Examples.
Files:
packages/core/src/components/mail/component.tspackages/core/src/components/mail/common/types.tspackages/core/src/components/mail/common/constants.tspackages/core/src/components/mail/common/keys.tspackages/core/src/components/mail/services/mail.service.tspackages/core/src/components/mail/services/template.service.tspackages/core/src/components/mail/services/generator.service.tspackages/core/src/components/mail/providers/mail-transporter.provider.tspackages/core/src/components/mail/providers/mail-queue-executor.provider.tspackages/core/src/components/mail/helpers/transporters/nodemail-transporter.helper.tspackages/core/src/components/mail/helpers/transporters/mailgun-transporter.helper.tspackages/core/src/components/mail/helpers/executors/direct-executor.helper.tspackages/core/src/components/mail/helpers/executors/internal-queue-executor.helper.tspackages/core/src/components/mail/helpers/executors/bull-mq-executor.helper.tspackages/core/src/components/mail/utilities/type.utility.tspackages/core/src/components/mail/utilities/verification.utility.ts
Quick reference
| Item | Value |
|---|---|
| Package | @venizia/ignis |
| Subpath | @venizia/ignis/mail |
| Component class | MailComponent |
| Runtimes | Both (Bun, Node.js) |
| Component | Purpose |
|---|---|
MailComponent | Registers mail services, transporters, generators, and the queue executor |
MailService | send(), sendBatch(), sendTemplate(), verify() |
TemplateEngineService | {{variable}} substitution engine, in-memory template registry |
NodemailerTransportHelper | SMTP transport via nodemailer |
MailgunTransportHelper | Mailgun HTTP API transport via mailgun.js |
DirectMailExecutorHelper | Runs the processor immediately, no queue |
InternalQueueMailExecutorHelper | In-memory queue (SequentialQueueHelper) |
BullMQMailExecutorHelper | Redis-backed queue, distributed workers |
MailTransportProvider | Factory: TMailOptions -> IMailTransport |
MailQueueExecutorProvider | Factory: IMailQueueExecutorConfig -> IMailQueueExecutor |
NumericCodeGenerator | Cryptographically random numeric codes |
RandomTokenGenerator | Cryptographically random base64url tokens |
DefaultVerificationDataGenerator | Composes both generators into an IVerificationData |
Import paths
import {
MailComponent,
MailKeys,
MailProviders,
MailErrorCodes,
MailDefaults,
MailExecutorErrors,
MailQueueExecutorTypes,
BullMQExecutorModes,
MailService,
TemplateEngineService,
NumericCodeGenerator,
RandomTokenGenerator,
DefaultVerificationDataGenerator,
MailTransportProvider,
MailQueueExecutorProvider,
} from '@venizia/ignis/mail';
import type {
TMailOptions,
IBaseMailOptions,
INodemailerMailOptions,
IMailgunMailOptions,
ICustomMailOptions,
IGenericMailOptions,
IMailService,
IMailTemplateEngine,
IMailMessage,
IMailSendResult,
IMailTransport,
IMailAttachment,
IMailQueueExecutor,
IMailQueueExecutorConfig,
IMailQueueOptions,
IMailQueueResult,
IMailProcessorResult,
ITemplate,
IVerificationCodeGenerator,
IVerificationTokenGenerator,
IVerificationDataGenerator,
IVerificationData,
IVerificationGenerationOptions,
TMailProvider,
TNodemailerConfig,
TMailgunConfig,
} from '@venizia/ignis/mail';Architecture
┌─────────────────────────────────────────────────┐
│ Your Application │
│ │
│ preConfigure() │
│ ├── binds MailKeys.MAIL_OPTIONS (required) │
│ ├── binds MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG │
│ │ (optional -- defaults to `direct`) │
│ └── registers MailComponent │
└───────────────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ MailComponent.binding() │
│ │
│ ├── initGenerators() (transient bindings) │
│ │ ├── NumericCodeGenerator │
│ │ ├── RandomTokenGenerator │
│ │ └── DefaultVerificationDataGenerator │
│ │ │
│ ├── initProviders() (singleton bindings) │
│ │ ├── MailTransportProvider │
│ │ └── MailQueueExecutorProvider │
│ │ │
│ ├── initServices() (singleton bindings) │
│ │ ├── MailService │
│ │ └── TemplateEngineService │
│ │ │
│ └── createAndBindInstances() │
│ ├── Transport Instance ◄── MAIL_OPTIONS │
│ └── Queue Executor ◄── QUEUE_CONFIG │
│ (or the direct │
│ default) │
└─────────────────────────────────────────────────┘MailService and the queue executor are independent consumers of the transport/config -- IMailQueueExecutor never calls MailService. See How it works on the Overview for that distinction.
Source: component.ts
Binding keys
| Key | Constant | Type | Required | Default |
|---|---|---|---|---|
@app/components/mail/options | MailKeys.MAIL_OPTIONS | TMailOptions | Yes | -- |
@app/components/mail/queue/executor-config | MailKeys.MAIL_QUEUE_EXECUTOR_CONFIG | IMailQueueExecutorConfig | No | { type: MailQueueExecutorTypes.DIRECT } |
@app/components/mail/service | MailKeys.MAIL_SERVICE | IMailService | No | MailService (singleton) |
@app/components/mail/services/template-engine | MailKeys.MAIL_TEMPLATE_ENGINE | IMailTemplateEngine | No | TemplateEngineService (singleton) |
@app/components/mail/transport-provider | MailKeys.MAIL_TRANSPORT_PROVIDER | TGetMailTransportFn | No | MailTransportProvider (singleton) |
@app/components/mail/transport-instance | MailKeys.MAIL_TRANSPORT_INSTANCE | IMailTransport | No | Created by the component |
@app/components/mail/queue-executor-provider | MailKeys.MAIL_QUEUE_EXECUTOR_PROVIDER | TGetMailQueueExecutorFn | No | MailQueueExecutorProvider (singleton) |
@app/components/mail/queue-executor-instance | MailKeys.MAIL_QUEUE_EXECUTOR_INSTANCE | IMailQueueExecutor | No | Created by the component |
@app/components/mail/verification/code-generator | MailKeys.MAIL_VERIFICATION_CODE_GENERATOR | IVerificationCodeGenerator | No | NumericCodeGenerator (transient) |
@app/components/mail/verification/token-generator | MailKeys.MAIL_VERIFICATION_TOKEN_GENERATOR | IVerificationTokenGenerator | No | RandomTokenGenerator (transient) |
@app/components/mail/verification/data-generator | MailKeys.MAIL_VERIFICATION_DATA_GENERATOR | IVerificationDataGenerator | No | DefaultVerificationDataGenerator (transient) |
IMPORTANT
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.
Source: common/keys.ts
Configuration
Transport options (TMailOptions)
A discriminated union on provider, extending IBaseMailOptions:
interface IBaseMailOptions {
from?: string;
fromName?: string;
}
interface INodemailerMailOptions extends IBaseMailOptions {
provider: 'nodemailer';
config: TNodemailerConfig; // SMTPTransport | SMTPTransport.Options | string
}
interface IMailgunMailOptions extends IBaseMailOptions {
provider: 'mailgun';
config: TMailgunConfig; // AnyType & { domain: string } -- also requires username, key at runtime
}
interface ICustomMailOptions extends IBaseMailOptions {
provider: 'custom';
config: IMailTransport; // Must implement send() and verify()
}
interface IGenericMailOptions extends IBaseMailOptions {
provider: string;
config: Record<string, AnyType>;
}
type TMailOptions =
| INodemailerMailOptions
| IMailgunMailOptions
| ICustomMailOptions
| IGenericMailOptions;Nodemailer (SMTP with basic auth):
{
provider: MailProviders.NODEMAILER,
from: 'noreply@example.com',
fromName: 'Example App',
config: {
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: { user: 'your-email@gmail.com', pass: 'your-app-password' },
},
}Nodemailer (OAuth2):
{
provider: MailProviders.NODEMAILER,
from: applicationEnvironment.get<string>('APP_ENV_MAIL_FROM') ?? 'noreply@example.com',
fromName: applicationEnvironment.get<string>('APP_ENV_MAIL_FROM_NAME') ?? 'Example App',
config: {
host: applicationEnvironment.get<string>('APP_ENV_MAIL_HOST') ?? 'smtp.gmail.com',
port: +(applicationEnvironment.get<number>('APP_ENV_MAIL_PORT') ?? 465),
secure: toBoolean(applicationEnvironment.get<boolean>('APP_ENV_MAIL_SECURE') ?? true),
auth: {
type: 'oauth2',
user: applicationEnvironment.get<string>('APP_ENV_MAIL_USER'),
clientId: applicationEnvironment.get<string>('APP_ENV_MAIL_CLIENT_ID'),
clientSecret: applicationEnvironment.get<string>('APP_ENV_MAIL_CLIENT_SECRET'),
refreshToken: applicationEnvironment.get<string>('APP_ENV_MAIL_REFRESH_TOKEN'),
},
},
}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:
APP_ENV_MAIL_HOST=smtp.gmail.com
APP_ENV_MAIL_PORT=465
APP_ENV_MAIL_SECURE=true
APP_ENV_MAIL_USER=your-email@gmail.com
APP_ENV_MAIL_CLIENT_ID=your-oauth2-client-id
APP_ENV_MAIL_CLIENT_SECRET=your-oauth2-client-secret
APP_ENV_MAIL_REFRESH_TOKEN=your-oauth2-refresh-tokenTIP
For Gmail OAuth2, follow Google's OAuth2 setup guide to obtain the client ID, secret, and refresh token.
Mailgun:
{
provider: MailProviders.MAILGUN,
from: 'noreply@example.com',
fromName: 'Example App',
config: {
username: 'api', // required -- mailgun.js client username
key: process.env.MAILGUN_API_KEY, // required -- Mailgun API key
domain: 'mg.example.com', // required
host: 'api.eu.mailgun.net', // optional -- EU region
},
}IMPORTANT
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.
Custom transport:
{
provider: MailProviders.CUSTOM,
from: 'noreply@example.com',
config: myTransportImplementingIMailTransport, // must have send() and verify()
}Generic provider (extensibility escape hatch):
{
provider: 'sendgrid',
from: 'noreply@example.com',
config: { apiKey: process.env.SENDGRID_API_KEY },
}WARNING
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.
Queue executor options (IMailQueueExecutorConfig)
interface IMailQueueExecutorConfig {
type: TConstValue<typeof MailQueueExecutorTypes>; // 'direct' | 'internal-queue' | 'bullmq'
internalQueue?: {
identifier: string;
};
bullmq?: {
redis: IRedisSingleHelperOptions;
queue: { identifier: string; name: string };
mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED -- no default
};
}// Direct (also the implicit default when MAIL_QUEUE_EXECUTOR_CONFIG is unbound)
{ type: 'direct' }
// Internal queue (in-memory)
{ type: 'internal-queue', internalQueue: { identifier: 'mail-internal-queue' } }
// BullMQ (Redis-backed)
{
type: 'bullmq',
bullmq: {
redis: { host: 'localhost', port: 6379, password: 'your-redis-password' },
queue: { identifier: 'mail-queue', name: 'mail-queue' },
mode: 'both', // 'queue-only' | 'worker-only' | 'both' -- required, no default
},
}NOTE
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).
Source: common/types.ts
Constants
| Constant | Value | Description |
|---|---|---|
MailDefaults.BATCH_CONCURRENCY | 5 | Default concurrent sends in sendBatch() |
MailDefaults.FALLBACK_FROM | 'noreply@example.com' | Used by getDefaultFrom() when options.from is unset |
MailExecutorErrors.PROCESSOR_NOT_SET | 'Processor not set. Call setProcessor() first.' | Thrown by all three queue executors |
MailQueueExecutorTypes.DIRECT | 'direct' | Immediate execution |
MailQueueExecutorTypes.INTERNAL_QUEUE | 'internal-queue' | In-memory queue |
MailQueueExecutorTypes.BULLMQ | 'bullmq' | Redis-backed queue |
BullMQExecutorModes.QUEUE_ONLY | 'queue-only' | Producer only (enqueue) |
BullMQExecutorModes.WORKER_ONLY | 'worker-only' | Consumer only (process) |
BullMQExecutorModes.BOTH | 'both' | Full duplex (produce + consume) |
MailQueueExecutorTypes and BullMQExecutorModes both carry a SCHEME_SET/MODE_SET and a static isValid():
MailQueueExecutorTypes.isValid('bullmq'); // true
MailQueueExecutorTypes.isValid('unknown'); // false
BullMQExecutorModes.isValid('both'); // true
BullMQExecutorModes.isValid('invalid'); // falseMailErrorCodes
Built through MessageCode.build(), so every value is lower-case (ApplicationError lower-cases whatever it is handed):
| Constant | Value | Description |
|---|---|---|
MailErrorCodes.INVALID_CONFIGURATION | 'core.mail.invalid_configuration' | Invalid or missing configuration (transport, template engine, subject, body) |
MailErrorCodes.SEND_FAILED | 'core.mail.send_failed' | Single email send failed |
MailErrorCodes.VERIFICATION_FAILED | 'core.mail.verification_failed' | Transport connection verification failed |
MailErrorCodes.INVALID_RECIPIENT | 'core.mail.invalid_recipient' | Missing or empty recipient address |
MailErrorCodes.BATCH_SEND_FAILED | 'core.mail.batch_send_failed' | Batch email operation failed |
MailErrorCodes.TEMPLATE_NOT_FOUND | 'core.mail.template_not_found' | Template name not found in registry |
Source: common/constants.ts
IMailService interface
interface IMailService {
send(message: IMailMessage): Promise<IMailSendResult>;
sendBatch(
messages: IMailMessage[],
options?: { concurrency?: number },
): Promise<IMailSendResult[]>;
sendTemplate(opts: {
templateName: string;
data: Record<string, any>;
recipients: string | string[];
options?: Partial<IMailMessage>;
}): Promise<IMailSendResult>;
verify(): Promise<boolean>;
}send(message)
validateMessage()throws for missingto, missingsubject, or missing bothtext/html(400, see below).- Merges
message.fromwithgetDefaultFrom()if unset. - Delegates to
transport.send(). - Returns
{ success, messageId, error? }. - If the transport throws and the error is not already an
ApplicationError, it is caught and re-thrown asMailErrorCodes.SEND_FAILED(500). AnApplicationErrorthrown earlier in the sametryblock (e.g. fromvalidateMessage()) is re-thrown as-is, unchanged.
sendBatch(messages, options?)
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.
sendTemplate(opts)
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.
verify()
Delegates to transport.verify(). If the transport throws, wraps it as MailErrorCodes.VERIFICATION_FAILED (500).
Protected methods (MailService)
validateMessage(message)
| Check | Error code | Status | Message |
|---|---|---|---|
to is falsy or an empty array | INVALID_RECIPIENT | 400 | Recipient email address is required |
subject is falsy | INVALID_CONFIGURATION | 400 | Email subject is required |
Both text and html are falsy | INVALID_CONFIGURATION | 400 | Email must have either text or html content |
getDefaultFrom()
- If
options.fromis unset, returnsMailDefaults.FALLBACK_FROM('noreply@example.com'). - Else if
options.fromNameis unset, returnsoptions.fromas-is. - Else returns
"${fromName}" <${from}>.
Source: services/mail.service.ts
IMailMessage interface
interface IMailMessage {
from?: string; // Uses getDefaultFrom() if not provided
to: string | string[];
cc?: string | string[];
bcc?: string | string[];
replyTo?: string;
subject: string;
text?: string;
html?: string;
attachments?: IMailAttachment[];
headers?: Record<string, string>;
requireValidate?: boolean; // Passed through to sendTemplate()'s render() call
[key: string]: any; // Open-ended -- provider-specific fields pass through
}| Field | Notes |
|---|---|
from | Falls back to getDefaultFrom(). Can be overridden per message (multi-tenant scenarios). |
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. |
replyTo | Nodemailer passes it directly; Mailgun maps it to h:Reply-To. |
subject | Rendered through the template engine when set via sendTemplate(). |
text, html | At least one is required (validateMessage()). |
attachments | See IMailAttachment below. |
headers | Nodemailer passes them directly; Mailgun prefixes every key with h:. |
requireValidate | true makes template rendering throw on missing placeholders instead of preserving them as literal text. Defaults to false. |
interface IMailAttachment {
filename?: string;
contentType?: string;
path?: string;
content?: string | Buffer | Readable;
cid?: string;
[key: string]: any;
}- File path:
{ filename: 'doc.pdf', path: '/path/to/doc.pdf' } - Buffer:
{ filename: 'data.txt', content: Buffer.from('...') } - Inline image:
{ filename: 'logo.png', path: '...', cid: 'logo' } - Mailgun maps each attachment to
{ filename, data: att.path ?? att.content ?? Buffer.from('') }.
Source: common/types.ts
IMailTemplateEngine interface
interface IMailTemplateEngine {
render(opts: {
templateData?: string;
templateName?: string;
data: Record<string, any>;
requireValidate?: boolean;
}): string;
registerTemplate(opts: { name: string; content: string }): void;
validateTemplateData(opts: { template: string; data: Record<string, any> }): {
isValid: boolean;
missingKeys: string[];
allKeys: string[];
};
getTemplate(name: string): ITemplate | undefined;
listTemplates(): ITemplate[];
hasTemplate(name: string): boolean;
removeTemplate(name: string): boolean;
}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.
| Method | Behavior |
|---|---|
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(). |
registerTemplate(opts) | options (on the class) accepts subject/description (Partial<ITemplate>). Overwrites an existing template with the same name. |
validateTemplateData(opts) | Extracts every unique {{key}} via /\{\{(\s*[\w.]+\s*)\}\}/g, deduplicates, resolves nested dot-notation values. Returns isValid, missingKeys, allKeys. |
getTemplate(name) | Returns undefined if not registered. |
listTemplates() | All templates, as an array of ITemplate. |
hasTemplate(name) | Registry membership check. |
removeTemplate(name) | Logs the removal, returns true/false. |
interface ITemplate {
name: string;
content?: string;
render?: (data: Record<string, AnyType>) => string;
subject?: string;
description?: string;
}The render field supports a custom per-template render function, though the built-in TemplateEngineService always uses content + renderSimpleTemplate() instead.
Source: services/template.service.ts
Transport layer
MailTransportProvider.value() returns a factory function that switches on TMailOptions.provider:
| Provider | Result |
|---|---|
'nodemailer' | NodemailerTransportHelper, backed by nodemailer |
'mailgun' | MailgunTransportHelper, backed by the Mailgun HTTP API (mailgun.js) |
'custom' | The config value itself, validated to implement IMailTransport |
| Any other string | Throws Unsupported mail provider: <provider> (INVALID_CONFIGURATION, 500) |
Type narrowing uses three private guards, each checking provider === X && 'config' in options:
private isNodemailerOptions(options: TMailOptions): options is INodemailerMailOptions
private isMailgunOptions(options: TMailOptions): options is IMailgunMailOptions
private isCustomOptions(options: TMailOptions): options is ICustomMailOptionsFor custom, an additional isMailTransport() utility check reports specifically which of send/verify is missing.
interface IMailTransport {
send(message: IMailMessage): Promise<IMailSendResult>;
verify(): Promise<boolean>;
close?(): Promise<void>;
}Peer dependency loading
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.
Nodemailer (NodemailerTransportHelper, extends BaseHelper):
configure()builds the transporter vianodemailer.createTransport(config).send()mapsIMailMessagefields to Nodemailer's mail options, joining array recipients with', '. Catches transport errors and returns{ success: false, error }-- never throws.verify()delegates totransporter.verify()(SMTP handshake). Catches errors and returnsfalse-- never throws.close()callstransporter.close().
Mailgun (MailgunTransportHelper, extends BaseHelper):
configure()callsvalidateConfig()before building the client:username,key, anddomainmust all be present inconfig, or it throwsInvalid Mailgun configuration | Missing required keys: <keys>(INVALID_CONFIGURATION, 500). This check runs even thoughTMailgunConfig's type only requiresdomain.send()convertsIMailMessageto Mailgun's format:tois coerced to an array,replyTobecomesh:Reply-To, every custom header is prefixedh:. Attachments map to{ filename, data: path ?? content ?? Buffer.from('') }. Catches send errors and returns{ success: false, error }-- never throws.verify()sends a test message toverify@<domain>witho:testmode: 'yes'(Mailgun has no dedicated verify endpoint). Catches errors and returnsfalse-- never throws.- No
close()-- the HTTP API is stateless.
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.
Source: helpers/transporters/
Queue executor implementations
All three implement:
interface IMailQueueExecutor {
enqueueVerificationEmail(email: string, options?: IMailQueueOptions): Promise<IMailQueueResult>;
setProcessor(processor: (email: string) => Promise<IMailProcessorResult>): void;
}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().
InternalQueueMailExecutorHelper (extends BaseHelper, constructor takes IInternalQueueMailExecutorOpts) -- wraps SequentialQueueHelper<IQueueJobPayload> from @venizia/ignis-helpers, autoDispatch: true.
- Job IDs:
job_<counter>_<timestamp>. options.delay > 0schedules the enqueue itself viasetTimeout, tracked in an internaldelayedJobs: Map<string, NodeJS.Timeout>.- Retry: on a thrown error or
{ success: false }from the processor, retries up tooptions.attempts ?? 3times.calculateBackoff(): nobackoffconfig ->1000msfixed;type: 'fixed'-> the rawdelay;type: 'exponential'->delay * 2 ** (attempt - 1). close()clears every pending delayed/retrysetTimeout-- without it, a live timer keeps the event loop open past shutdown.- No persistence across restarts.
BullMQMailExecutorHelper (extends BaseHelper, constructor takes IBullMQMailExecutorOpts) -- wraps BullMQHelper (@venizia/ignis-helpers/bullmq) and a RedisSingleHelper connection.
| Mode | Queue created | Workers created | Can enqueue | Can process |
|---|---|---|---|---|
'queue-only' | Yes | No (setProcessor() skips worker creation, logs a warning) | Yes -- processor not required | No |
'worker-only' | No | Yes | No (throws) | Yes |
'both' | Yes | Yes | Yes -- processor required | Yes |
setProcessor(processor, opts?)isasync. It clears all existing workers, then createsopts?.numberOfWorkers ?? 1workers, each withconcurrencyPerWorker ?? 5andlockDuration ?? 30000ms. Inqueue-onlymode it stores the processor and returns without creating any worker.enqueueVerificationEmail()throwsCannot enqueue jobs in worker-only mode...inworker-onlymode; throwsQueue helper not initialized...if the queue was never created (should not happen in a queue-enabled mode); throwsPROCESSOR_NOT_SETonly whenmode !== '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.- Dynamic workers:
addWorker(opts)(requires a processor, unique identifier, default concurrency5, default lock duration30000ms),removeWorker(index)(closes then splices, returnsfalseif out of range),clearWorkers()(closes and empties, called internally bysetProcessor()),getWorkerCount(),getMode(). 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.
interface IBullMQMailExecutorOpts {
redis: IRedisSingleHelperOptions; // from @venizia/ignis-helpers
queue: { identifier: string; name: string };
mode: TConstValue<typeof BullMQExecutorModes>; // REQUIRED, no default
}Source: helpers/executors/
Additional interfaces
interface IMailSendResult {
success: boolean;
messageId?: string;
response?: any;
error?: string;
}
interface IMailQueueOptions {
priority?: number;
delay?: number;
attempts?: number;
backoff?: { type: 'fixed' | 'exponential'; delay: number };
}
interface IMailQueueResult {
jobId?: string;
queued: boolean; // false for direct execution, true for internal-queue and bullmq
message: string;
result?: IMailProcessorResult; // populated only for direct execution (synchronous processor)
}
interface IMailProcessorResult {
success: boolean;
message: string;
expiresInMinutes: number;
nextResendAt?: string;
}
interface IVerificationData {
verificationCode: string;
codeGeneratedAt: string;
codeExpiresAt: string;
codeAttempts: number;
verificationToken: string;
tokenGeneratedAt: string;
tokenExpiresAt: string;
lastCodeSentAt: string;
}
interface IVerificationGenerationOptions {
codeLength: number; // all four fields required, no defaults
tokenBytes: number;
codeExpiryMinutes: number;
tokenExpiryHours: number;
}
interface IQueueJobPayload {
id: string;
email: string;
options?: IMailQueueOptions;
attempts: number;
scheduledAt: number;
}
interface IInternalQueueMailExecutorOpts {
identifier: string;
}Source: common/types.ts
Utility functions
// type.utility.ts
function isMailTransport(value: AnyType): value is IMailTransport;
function isValidMailOptions(options: AnyType): options is TMailOptions;
// verification.utility.ts
function getExpiryTime(minutes: number): Date; // Date `minutes` minutes from now
function getExpiryTimeInHours(hours: number): Date; // Date `hours` hours from nowisMailTransport()checks thatsendandverifyare functions, and thatcloseis either a function orundefined.isValidMailOptions()checks thatprovideris a string andconfigis truthy -- it does not validate the shape ofconfigagainst the specific provider.
Source: utilities/
See also
- Overview -- quick start, imports, common configuration tasks
- Usage & Examples -- sending emails, templates, queue executors, verification generators
- Error Reference -- error codes and troubleshooting
- Queue Helper --
SequentialQueueHelperandBullMQHelper - Redis Helper --
RedisSingleHelper,IRedisSingleHelperOptions