feat(cloud): create subscribers

This commit is contained in:
Henry
2026-07-31 16:25:25 +01:00
parent 4291d7bc8c
commit d6aa82e070
5 changed files with 96 additions and 10 deletions

View File

@@ -53,6 +53,9 @@ export const env = createEnv({
LINKEDIN_CLIENT_ID: z.string().optional(),
LINKEDIN_CLIENT_SECRET: z.string().optional(),
NOVU_API_KEY: z.string().optional(),
SUBSCRIBER_API_URL: z.string().url().optional(),
SUBSCRIBER_API_KEY: z.string().optional(),
SUBSCRIBER_ENVIRONMENT_ID: z.string().optional(),
EMAIL_UNSUBSCRIBE_SECRET: z.string().optional(),
// Generic OIDC Provider
OIDC_CLIENT_ID: z.string().optional(),

View File

@@ -64,6 +64,9 @@ services:
- NOVU_API_KEY=${NOVU_API_KEY}
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL}
- EMAIL_UNSUBSCRIBE_SECRET=${EMAIL_UNSUBSCRIBE_SECRET}
- SUBSCRIBER_API_URL=${SUBSCRIBER_API_URL}
- SUBSCRIBER_API_KEY=${SUBSCRIBER_API_KEY}
- SUBSCRIBER_ENVIRONMENT_ID=${SUBSCRIBER_ENVIRONMENT_ID}
# S3 storage
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}

View File

@@ -6,14 +6,14 @@ import { env } from "next-runtime-env";
import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import { notificationClient } from "@kan/email";
import { createSubscriber, notificationClient } from "@kan/email";
import { createLogger } from "@kan/logger";
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
const log = createLogger("auth");
import { downloadImage } from "./utils";
const log = createLogger("auth");
type BetterAuthUser = {
id: string;
createdAt: Date;
@@ -95,18 +95,26 @@ export function createDatabaseHooks(db: dbClient) {
}
if (notificationClient) {
const [firstName, ...rest] = (user.name || "")
.split(" ")
.filter(Boolean);
const lastName = rest.length ? rest.join(" ") : undefined;
try {
const [firstName, ...rest] = (user.name || "")
.split(" ")
.filter(Boolean);
const lastName = rest.length ? rest.join(" ") : undefined;
const avatarUrl = avatarKey
? `${env("NEXT_PUBLIC_STORAGE_URL")}/${env("NEXT_PUBLIC_AVATAR_BUCKET_NAME")}/${avatarKey}`
: undefined;
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
log.info({ workflowId: "user-signup", userId: user.id, email: user.email }, "Triggering Novu workflow");
log.info(
{
workflowId: "user-signup",
userId: user.id,
email: user.email,
},
"Triggering Novu workflow",
);
await notificationClient.trigger({
to: {
subscriberId: user.id,
@@ -126,7 +134,10 @@ export function createDatabaseHooks(db: dbClient) {
},
workflowId: "user-signup",
});
log.info({ workflowId: "user-signup", userId: user.id }, "Novu workflow triggered");
log.info(
{ workflowId: "user-signup", userId: user.id },
"Novu workflow triggered",
);
await notificationClient.subscribers.credentials.update(
{
@@ -139,7 +150,22 @@ export function createDatabaseHooks(db: dbClient) {
user.id,
);
} catch (error) {
log.error({ err: error }, "Error adding user to notification client");
log.error(
{ err: error },
"Error adding user to notification client",
);
}
try {
await createSubscriber({
email: user.email,
externalId: user.id,
firstName,
lastName,
name: user.name,
});
} catch (error) {
log.error({ err: error }, "Error creating subscriber");
}
}
},

View File

@@ -2,3 +2,4 @@ export const name = "email";
export { sendEmail } from "./sendEmail";
export { notificationClient } from "./notificationClient";
export { createSubscriber } from "./subscriberClient";

View File

@@ -0,0 +1,53 @@
import { createLogger } from "@kan/logger";
const log = createLogger("subscriberClient");
export const subscriberClient =
process.env.NEXT_PUBLIC_KAN_ENV === "cloud" &&
process.env.SUBSCRIBER_API_URL &&
process.env.SUBSCRIBER_API_KEY &&
process.env.SUBSCRIBER_ENVIRONMENT_ID
? {
apiUrl: process.env.SUBSCRIBER_API_URL,
apiKey: process.env.SUBSCRIBER_API_KEY,
environmentId: process.env.SUBSCRIBER_ENVIRONMENT_ID,
}
: null;
interface CreateSubscriberInput {
email: string;
externalId: string;
firstName?: string;
lastName?: string;
name?: string;
}
export async function createSubscriber(input: CreateSubscriberInput) {
if (!subscriberClient) return;
try {
const response = await fetch(
`${subscriberClient.apiUrl}/environments/${subscriberClient.environmentId}/subscribers`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": subscriberClient.apiKey,
},
body: JSON.stringify(input),
},
);
if (!response.ok) {
log.error(
{
status: response.status,
body: await response.text().catch(() => undefined),
},
"Failed to create subscriber.dev subscriber",
);
}
} catch (error) {
log.error({ err: error }, "Error creating subscriber.dev subscriber");
}
}