Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/emails/services/TransactionalEmailQueue.server.ts
T

93 lines
2.5 KiB
TypeScript

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<string, unknown>;
/**
* 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<typeof queueNames.email>;
};
export async function queueTransactionalEmail(
input: QueueTransactionalEmailInput,
dependencies: QueueTransactionalEmailDependencies = {}
): Promise<QueueTransactionalEmailResult> {
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();
}
}
}