167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
import { Controller, Post } from "@overnightjs/core";
|
|
import type { Event } from "@prisma/client";
|
|
import type { Request, Response } from "express";
|
|
import signale from "signale";
|
|
import { prisma } from "../../../database/prisma";
|
|
import { ActionService } from "../../../services/ActionService";
|
|
import { ProjectService } from "../../../services/ProjectService";
|
|
|
|
const eventMap = {
|
|
Bounce: "BOUNCED",
|
|
Delivery: "DELIVERED",
|
|
Open: "OPENED",
|
|
Complaint: "COMPLAINT",
|
|
Click: "CLICKED",
|
|
} as const;
|
|
|
|
@Controller("sns")
|
|
export class SNSWebhook {
|
|
@Post()
|
|
public async receiveSNSWebhook(req: Request, res: Response) {
|
|
try {
|
|
const body = JSON.parse(req.body.Message);
|
|
|
|
const email = await prisma.email.findUnique({
|
|
where: { messageId: body.mail.messageId },
|
|
include: {
|
|
contact: true,
|
|
action: { include: { template: { include: { events: true } } } },
|
|
campaign: { include: { events: true } },
|
|
},
|
|
});
|
|
|
|
if (!email) {
|
|
return res.status(200).json({});
|
|
}
|
|
|
|
const project = await ProjectService.id(email.contact.projectId);
|
|
|
|
if (!project) {
|
|
return res.status(200).json({ success: false });
|
|
}
|
|
|
|
// The email was a transactional email
|
|
if (email.projectId) {
|
|
if (body.eventType === "Click") {
|
|
signale.success(`Click received for ${email.contact.email} from ${project.name}`);
|
|
await prisma.click.create({
|
|
data: { emailId: email.id, link: body.click.link },
|
|
});
|
|
}
|
|
|
|
if (body.eventType === "Complaint") {
|
|
signale.warn(`Complaint received for ${email.contact.email} from ${project.name}`);
|
|
}
|
|
|
|
if (body.eventType === "Bounce") {
|
|
signale.warn(`Bounce received for ${email.contact.email} from ${project.name}`);
|
|
}
|
|
|
|
await prisma.email.update({
|
|
where: { messageId: body.mail.messageId },
|
|
data: {
|
|
status: eventMap[body.eventType as "Bounce" | "Delivery" | "Open" | "Complaint"],
|
|
},
|
|
});
|
|
|
|
return res.status(200).json({ success: true });
|
|
}
|
|
|
|
if (body.eventType === "Complaint" || body.eventType === "Bounce") {
|
|
signale.warn(
|
|
`${body.eventType === "Complaint" ? "Complaint" : "Bounce"} received for ${email.contact.email} from ${project.name}`,
|
|
);
|
|
|
|
await prisma.email.update({
|
|
where: { messageId: body.mail.messageId },
|
|
data: { status: eventMap[body.eventType as "Bounce" | "Complaint"] },
|
|
});
|
|
|
|
await prisma.contact.update({
|
|
where: { id: email.contactId },
|
|
data: { subscribed: false },
|
|
});
|
|
|
|
return res.status(200).json({ success: true });
|
|
}
|
|
|
|
if (body.eventType === "Click") {
|
|
signale.success(`Click received for ${email.contact.email} from ${project.name}`);
|
|
|
|
await prisma.click.create({
|
|
data: { emailId: email.id, link: body.click.link },
|
|
});
|
|
|
|
return res.status(200).json({ success: true });
|
|
}
|
|
|
|
let event: Event | undefined;
|
|
|
|
if (email.action) {
|
|
event = email.action.template.events.find((e) =>
|
|
e.name.includes(
|
|
(body.eventType as "Bounce" | "Delivery" | "Open" | "Complaint" | "Click") === "Delivery"
|
|
? "delivered"
|
|
: "opened",
|
|
),
|
|
);
|
|
}
|
|
|
|
if (email.campaign) {
|
|
event = email.campaign.events.find((e) =>
|
|
e.name.includes(
|
|
(body.eventType as "Bounce" | "Delivery" | "Open" | "Complaint" | "Click") === "Delivery"
|
|
? "delivered"
|
|
: "opened",
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!event) {
|
|
return res.status(200).json({ success: false });
|
|
}
|
|
|
|
switch (body.eventType as "Delivery" | "Open") {
|
|
case "Delivery":
|
|
signale.success(`Delivery received for ${email.contact.email} from ${project.name}`);
|
|
await prisma.email.update({
|
|
where: { messageId: body.mail.messageId },
|
|
data: { status: "DELIVERED" },
|
|
});
|
|
|
|
await prisma.trigger.create({
|
|
data: { contactId: email.contactId, eventId: event.id },
|
|
});
|
|
|
|
break;
|
|
case "Open":
|
|
signale.success(`Open received for ${email.contact.email} from ${project.name}`);
|
|
await prisma.email.update({
|
|
where: { messageId: body.mail.messageId },
|
|
data: { status: "OPENED" },
|
|
});
|
|
await prisma.trigger.create({
|
|
data: { contactId: email.contactId, eventId: event.id },
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
if (email.action) {
|
|
void ActionService.trigger({ event, contact: email.contact, project });
|
|
}
|
|
} catch (e) {
|
|
if (req.body.SubscribeURL) {
|
|
signale.info("--------------");
|
|
signale.info("SNS Topic Confirmation URL:");
|
|
signale.info(req.body.SubscribeURL);
|
|
signale.info("--------------");
|
|
} else {
|
|
signale.error(e);
|
|
}
|
|
}
|
|
|
|
return res.status(200).json({ success: true });
|
|
}
|
|
}
|