Files
plunk/packages/api/src/controllers/Webhooks/Incoming/SNS.ts
T

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 });
}
}