Skip to content

Commit caaa7e5

Browse files
committed
Send a “sign-up” event to Loops
1 parent cec20b9 commit caaa7e5

File tree

3 files changed

+97
-12
lines changed

3 files changed

+97
-12
lines changed

apps/webapp/app/env.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ const EnvironmentSchema = z.object({
164164
ALERT_RESEND_API_KEY: z.string().optional(),
165165

166166
MAX_SEQUENTIAL_INDEX_FAILURE_COUNT: z.coerce.number().default(96),
167+
168+
LOOPS_API_KEY: z.string().optional(),
167169
});
168170

169171
export type Environment = z.infer<typeof EnvironmentSchema>;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { env } from "~/env.server";
2+
import { logger } from "./logger.server";
3+
4+
class LoopsClient {
5+
constructor(private readonly apiKey: string) {}
6+
7+
async userCreated({
8+
userId,
9+
email,
10+
name,
11+
}: {
12+
userId: string;
13+
email: string;
14+
name: string | null;
15+
}) {
16+
logger.info(`Loops send "sign-up" event`, { userId, email, name });
17+
return this.#sendEvent({
18+
email,
19+
userId,
20+
firstName: name?.split(" ").at(0),
21+
eventName: "sign-up",
22+
});
23+
}
24+
25+
async #sendEvent({
26+
email,
27+
userId,
28+
firstName,
29+
eventName,
30+
eventProperties,
31+
}: {
32+
email: string;
33+
userId: string;
34+
firstName?: string;
35+
eventName: string;
36+
eventProperties?: Record<string, string | number | boolean>;
37+
}) {
38+
const options = {
39+
method: "POST",
40+
headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
41+
body: JSON.stringify({
42+
email,
43+
userId,
44+
firstName,
45+
eventName,
46+
eventProperties,
47+
}),
48+
};
49+
50+
try {
51+
const response = await fetch("https://app.loops.so/api/v1/events/send", options);
52+
53+
if (!response.ok) {
54+
logger.error(`Loops sendEvent ${eventName} bad status`, { status: response.status });
55+
return false;
56+
}
57+
58+
const responseBody = (await response.json()) as any;
59+
60+
if (!responseBody.success) {
61+
logger.error(`Loops sendEvent ${eventName} failed response`, {
62+
message: responseBody.message,
63+
});
64+
return false;
65+
}
66+
67+
return true;
68+
} catch (error) {
69+
logger.error(`Loops sendEvent ${eventName} failed`, { error });
70+
return false;
71+
}
72+
}
73+
}
74+
75+
export const loopsClient = env.LOOPS_API_KEY ? new LoopsClient(env.LOOPS_API_KEY) : null;

apps/webapp/app/services/telemetry.server.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Organization } from "~/models/organization.server";
77
import type { Project } from "~/models/project.server";
88
import type { User } from "~/models/user.server";
99
import { singleton } from "~/utils/singleton";
10+
import { loopsClient } from "./loops.server";
1011

1112
type Options = {
1213
postHogApiKey?: string;
@@ -39,18 +40,19 @@ class Telemetry {
3940

4041
user = {
4142
identify: ({ user, isNewUser }: { user: User; isNewUser: boolean }) => {
42-
if (this.#posthogClient === undefined) return;
43-
this.#posthogClient.identify({
44-
distinctId: user.id,
45-
properties: {
46-
email: user.email,
47-
name: user.name,
48-
authenticationMethod: user.authenticationMethod,
49-
admin: user.admin,
50-
createdAt: user.createdAt,
51-
isNewUser,
52-
},
53-
});
43+
if (this.#posthogClient) {
44+
this.#posthogClient.identify({
45+
distinctId: user.id,
46+
properties: {
47+
email: user.email,
48+
name: user.name,
49+
authenticationMethod: user.authenticationMethod,
50+
admin: user.admin,
51+
createdAt: user.createdAt,
52+
isNewUser,
53+
},
54+
});
55+
}
5456
if (isNewUser) {
5557
this.#capture({
5658
userId: user.id,
@@ -64,6 +66,12 @@ class Telemetry {
6466
},
6567
});
6668

69+
loopsClient?.userCreated({
70+
userId: user.id,
71+
email: user.email,
72+
name: user.name,
73+
});
74+
6775
this.#triggerClient?.sendEvent({
6876
name: "user.created",
6977
payload: {

0 commit comments

Comments
 (0)