Send notifications through third-party providers.
npm install @clipboard-health/notifications
Export a NotificationJobEnqueuer
instance:
import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
import { BackgroundJobsService } from "./setup";
// Provide this in your microservice.
export const notificationJobEnqueuer = new NotificationJobEnqueuer({
adapter: new BackgroundJobsService(),
});
Enqueue your job:
import {
type ExampleNotificationEnqueueData,
ExampleNotificationJob,
} from "./exampleNotification.job";
import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
async function enqueueNotificationJob() {
await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationEnqueueData>(
ExampleNotificationJob,
{
// Set expiresAt at enqueue-time so it remains stable across job retries.
expiresAt: minutesFromNow(60).toISOString(),
// Set idempotencyKey at enqueue-time so it remains stable across job retries.
idempotencyKey: {
resourceId: "event-123",
},
// Set recipients at enqueue-time so they respect our notification provider's limits.
recipients: ["user-1"],
workflowKey: "event-starting-reminder",
// Any additional enqueue-time data passed to the job:
workplaceId: "workplace-123",
},
);
}
// eslint-disable-next-line unicorn/prefer-top-level-await
void enqueueNotificationJob();
function minutesFromNow(minutes: number) {
return new Date(Date.now() + minutes * 60_000);
}
Implement your job, which should be minimal, calling off to a service to send the actual notification:
import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
import {
type NotificationEnqueueData,
type NotificationJobData,
} from "@clipboard-health/notifications";
import { isFailure, toError } from "@clipboard-health/util-ts";
import { type ExampleNotificationService } from "./exampleNotification.service";
interface ExampleNotificationData {
workplaceId: string;
}
export type ExampleNotificationEnqueueData = NotificationEnqueueData & ExampleNotificationData;
export type ExampleNotificationJobData = NotificationJobData & ExampleNotificationData;
export class ExampleNotificationJob implements BaseHandler<ExampleNotificationJobData> {
constructor(private readonly service: ExampleNotificationService) {}
async perform(data: ExampleNotificationJobData, job: { attemptsCount: number }) {
const result = await this.service.sendNotification({
...data,
// Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
attempt: job.attemptsCount + 1,
});
if (isFailure(result)) {
throw toError(result.error);
}
}
}
Trigger the job in your service:
import { type NotificationClient } from "@clipboard-health/notifications";
import { type ExampleNotificationJobData } from "./exampleNotification.job";
type ExampleNotificationDo = ExampleNotificationJobData & { attempt: number };
export class ExampleNotificationService {
constructor(private readonly client: NotificationClient) {}
async sendNotification(params: ExampleNotificationDo) {
const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
// Assume this comes from a database and, for example, are used as template variables...
const data = { favoriteColor: "blue", secret: "2" };
return await this.client.trigger({
attempt,
body: {
recipients,
data,
workplaceId,
},
expiresAt: new Date(expiresAt),
idempotencyKey,
key: workflowKey,
keysToRedact: ["secret"],
workflowKey,
});
}
}
See package.json
scripts
for a list of commands.