Improve Self host setup (#30)

* Add self host setup

* Improve blunders

* Move to bull mq

* More changes

* Add example code for sending test emails
This commit is contained in:
KM Koushik
2024-06-24 08:21:37 +10:00
committed by GitHub
parent 8a2769621c
commit f77a8829be
67 changed files with 1771 additions and 688 deletions

View File

@@ -1,14 +1,22 @@
"use client";
import { UAParser } from "ua-parser-js";
import { api } from "~/trpc/react";
import { Separator } from "@unsend/ui/src/separator";
import { EmailStatusBadge, EmailStatusIcon } from "./email-status-badge";
import { formatDate } from "date-fns";
import { EmailStatus } from "@prisma/client";
import { JsonValue } from "@prisma/client/runtime/library";
import { SesBounce, SesDeliveryDelay } from "~/types/aws-types";
import {
SesBounce,
SesClick,
SesComplaint,
SesDeliveryDelay,
SesOpen,
} from "~/types/aws-types";
import {
BOUNCE_ERROR_MESSAGES,
COMPLAINT_ERROR_MESSAGES,
DELIVERY_DELAY_ERRORS,
} from "~/lib/constants/ses-errors";
@@ -39,7 +47,7 @@ export default function EmailDetails({ emailId }: { emailId: string }) {
<span className="w-[65px] text-muted-foreground ">Subject</span>
<span>{emailQuery.data?.subject}</span>
</div>
<div className=" dark:bg-slate-200 h-[300px] overflow-auto text-black rounded">
<div className=" dark:bg-slate-200 h-[250px] overflow-auto text-black rounded">
<div
className="px-4 py-4 overflow-auto"
dangerouslySetInnerHTML={{ __html: emailQuery.data?.html ?? "" }}
@@ -47,17 +55,20 @@ export default function EmailDetails({ emailId }: { emailId: string }) {
</div>
</div>
<div className=" border rounded-lg w-full ">
<div className=" p-4 flex flex-col gap-8">
<div className=" p-4 flex flex-col gap-8 w-full">
<div className="font-medium">Events History</div>
<div className="flex items-stretch px-4">
<div className="flex items-stretch px-4 w-full">
<div className="border-r border-dashed" />
<div className="flex flex-col gap-12">
<div className="flex flex-col gap-12 w-full">
{emailQuery.data?.emailEvents.map((evt) => (
<div key={evt.status} className="flex gap-5 items-start">
<div
key={evt.status}
className="flex gap-5 items-start w-full"
>
<div className=" -ml-2.5">
<EmailStatusIcon status={evt.status} />
</div>
<div className="-mt-[0.125rem]">
<div className="-mt-[0.125rem] w-full">
<div className=" capitalize font-medium">
<EmailStatusBadge status={evt.status} />
</div>
@@ -104,26 +115,88 @@ const EmailStatusText = ({
_errorData.bounceType;
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 w-full">
<p>{getErrorMessage(_errorData)}</p>
<div className="flex gap-2 ">
<div className="w-1/2">
<p className="text-sm text-muted-foreground">Type </p>
<p>{_errorData.bounceType}</p>
<div className="rounded-xl p-4 bg-muted/20 flex flex-col gap-4">
<div className="flex gap-2 w-full">
<div className="w-1/2">
<p className="text-sm text-muted-foreground">Type</p>
<p>{_errorData.bounceType}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Sub Type</p>
<p>{_errorData.bounceSubType}</p>
</div>
</div>
<div>
<p className="text-sm text-muted-foreground">Sub Type </p>
<p>{_errorData.bounceSubType}</p>
<p className="text-sm text-muted-foreground">SMTP response</p>
<p>{_errorData.bouncedRecipients[0]?.diagnosticCode}</p>
</div>
</div>
<div>
<p className="text-sm text-muted-foreground">SMTP response</p>
<p>{_errorData.bouncedRecipients[0]?.diagnosticCode}</p>
</div>
</div>
);
} else if (status === "FAILED") {
const _errorData = data as unknown as { error: string };
return <div>{_errorData.error}</div>;
} else if (status === "OPENED") {
const _data = data as unknown as SesOpen;
const userAgent = getUserAgent(_data.userAgent);
return (
<div className="w-full rounded-xl p-4 bg-muted/20 mt-4">
<div className="flex w-full ">
{userAgent.os.name ? (
<div className="w-1/2">
<p className="text-sm text-muted-foreground">OS</p>
<p>{userAgent.os.name}</p>
</div>
) : null}
{userAgent.browser.name ? (
<div>
<p className="text-sm text-muted-foreground">Browser</p>
<p>{userAgent.browser.name}</p>
</div>
) : null}
</div>
</div>
);
} else if (status === "CLICKED") {
const _data = data as unknown as SesClick;
const userAgent = getUserAgent(_data.userAgent);
return (
<div className="w-full mt-4 flex flex-col gap-4 rounded-xl p-4 bg-muted/20">
<div className="flex w-full ">
{userAgent.os.name ? (
<div className="w-1/2">
<p className="text-sm text-muted-foreground">OS </p>
<p>{userAgent.os.name}</p>
</div>
) : null}
{userAgent.browser.name ? (
<div>
<p className="text-sm text-muted-foreground">Browser </p>
<p>{userAgent.browser.name}</p>
</div>
) : null}
</div>
<div className="w-full">
<p className="text-sm text-muted-foreground">URL</p>
<p>{_data.link}</p>
</div>
</div>
);
} else if (status === "COMPLAINED") {
const _errorData = data as unknown as SesComplaint;
return (
<div className="flex flex-col gap-4 w-full">
<p>{getComplaintMessage(_errorData.complaintFeedbackType)}</p>
</div>
);
}
return <div>{status}</div>;
return <div className="w-full">{status}</div>;
};
const getErrorMessage = (data: SesBounce) => {
@@ -148,3 +221,18 @@ const getErrorMessage = (data: SesBounce) => {
return BOUNCE_ERROR_MESSAGES.Undetermined;
}
};
const getComplaintMessage = (errorType: string) => {
return COMPLAINT_ERROR_MESSAGES[
errorType as keyof typeof COMPLAINT_ERROR_MESSAGES
];
};
const getUserAgent = (userAgent: string) => {
const parser = new UAParser(userAgent);
return {
browser: parser.getBrowser(),
os: parser.getOS(),
device: parser.getDevice(),
};
};