import { buildTransactionalMailJob, enqueueJob, queueNames, resolveRedisConnectionOptions, resolveRedisConnectionOptionsFromProcessEnv, type RedisConnectionEnv, type TypedQueue, } from "@toolctrl/queue"; export type QueueTransactionalEmailInput = { to: string; subject?: string; body?: string; alias?: string; tenantId?: string; userId?: string; data?: Record; /** * Explicit BullMQ priority for this mail job. Use `"critical"` for * auth-flow mails (password-reset, magic-link, invitation, verification) * so they preempt low-priority batch jobs regardless of tenant plan. */ priorityOverride?: "critical" | "high" | "normal" | "low"; }; export type QueueTransactionalEmailResult = | { enqueued: true; jobId?: string; }; type QueueTransactionalEmailDependencies = { env?: RedisConnectionEnv; queue?: TypedQueue; }; export async function queueTransactionalEmail( input: QueueTransactionalEmailInput, dependencies: QueueTransactionalEmailDependencies = {} ): Promise { let queue = dependencies.queue; let shouldCloseQueue = false; const connection = queue ? undefined : dependencies.env ? resolveRedisConnectionOptions({ env: dependencies.env }) : resolveRedisConnectionOptionsFromProcessEnv(); if (!queue && !connection) { throw new Error("Redis connection required to enqueue transactional email."); } if (!queue && connection) { const queueRuntimeModule = "@toolctrl/queue/runtime"; const { createQueue } = (await import(/* @vite-ignore */ queueRuntimeModule)) as typeof import("@toolctrl/queue/runtime"); queue = createQueue(queueNames.email, { connection, }); shouldCloseQueue = true; } if (!queue) { throw new Error("Email queue was not initialized."); } try { const descriptor = buildTransactionalMailJob({ to: [input.to], subject: input.subject, html: input.body, templateData: input.data, tenantId: input.tenantId, userId: input.userId, metadata: { alias: input.alias, }, }); const job = await enqueueJob(queue, descriptor, { ...(input.tenantId !== undefined ? { tenantId: input.tenantId } : {}), ...(input.priorityOverride !== undefined ? { priorityOverride: input.priorityOverride } : {}), }); return { enqueued: true, jobId: job.id, }; } finally { if (shouldCloseQueue) { await queue.close(); } } }