Skip to content

Commit

Permalink
Merge pull request #95 from useplunk/dev-driaug-sender-details-overwrite
Browse files Browse the repository at this point in the history
Feat: Added ability to overwrite sender details
  • Loading branch information
driaug authored Sep 24, 2024
2 parents 2130811 + e2d0d0e commit a4b5069
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plunk",
"version": "1.0.5",
"version": "1.0.6",
"private": true,
"license": "agpl-3.0",
"workspaces": {
Expand Down
13 changes: 11 additions & 2 deletions packages/api/src/controllers/Tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export class Tasks {
let subject = "";
let body = "";

let email = "";
let name = "";

if (action) {
const { template, notevents } = action;

Expand All @@ -52,6 +55,9 @@ export class Tasks {
}
}

email = project.verified && project.email ? template.email ?? project.email : "[email protected]";
name = template.from ?? project.from ?? project.name;

({ subject, body } = EmailService.format({
subject: template.subject,
body: template.body,
Expand All @@ -62,6 +68,9 @@ export class Tasks {
},
}));
} else if (campaign) {
email = project.verified && project.email ? campaign.email ?? project.email : "[email protected]";
name = campaign.from ?? project.from ?? project.name;

({ subject, body } = EmailService.format({
subject: campaign.subject,
body: campaign.body,
Expand All @@ -75,8 +84,8 @@ export class Tasks {

const { messageId } = await EmailService.send({
from: {
name: project.from ?? project.name,
email: project.verified && project.email ? project.email : "[email protected]",
name,
email,
},
to: [contact.email],
content: {
Expand Down
28 changes: 25 additions & 3 deletions packages/api/src/controllers/v1/Campaigns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CampaignSchemas, UtilitySchemas } from "@plunk/shared";
import dayjs from "dayjs";
import type { Request, Response } from "express";
import { prisma } from "../../database/prisma";
import { HttpException, NotFound } from "../../exceptions";
import { HttpException, NotAllowed, NotFound } from "../../exceptions";
import { type IJwt, type ISecret, isAuthenticated, isValidSecretKey } from "../../middleware/auth";
import { CampaignService } from "../../services/CampaignService";
import { ContactService } from "../../services/ContactService";
Expand Down Expand Up @@ -160,6 +160,8 @@ export class Campaigns {
subject: campaign.subject,
body: campaign.body,
style: campaign.style,
email: campaign.email,
from: campaign.from,
},
});

Expand All @@ -180,7 +182,15 @@ export class Campaigns {
throw new NotFound("project");
}

let { subject, body, recipients, style } = CampaignSchemas.create.parse(req.body);
let { subject, body, recipients, style, email, from } = CampaignSchemas.create.parse(req.body);

if (email && !project.verified) {
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
}

if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
throw new NotAllowed("The sender address must be the same domain as the project's email address");
}

if (recipients.length === 1 && recipients[0] === "all") {
const projectContacts = await prisma.contact.findMany({
Expand All @@ -197,6 +207,8 @@ export class Campaigns {
subject,
body,
style,
from: from === "" ? null : from,
email: email === "" ? null : email,
},
});

Expand Down Expand Up @@ -253,7 +265,15 @@ export class Campaigns {
throw new NotFound("project");
}

let { id, subject, body, recipients, style } = CampaignSchemas.update.parse(req.body);
let { id, subject, body, recipients, style, email, from } = CampaignSchemas.update.parse(req.body);

if (email && !project.verified) {
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
}

if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
throw new NotAllowed("The sender address must be the same domain as the project's email address");
}

if (recipients.length === 1 && recipients[0] === "all") {
const projectContacts = await prisma.contact.findMany({
Expand All @@ -276,6 +296,8 @@ export class Campaigns {
subject,
body,
style,
from: from === "" ? null : from,
email: email === "" ? null : email,
},
include: {
recipients: { select: { id: true } },
Expand Down
33 changes: 30 additions & 3 deletions packages/api/src/controllers/v1/Templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export class Templates {
body: template.body,
type: template.type,
style: template.style,
email: template.email,
from: template.from,
},
});

Expand Down Expand Up @@ -101,7 +103,15 @@ export class Templates {
throw new NotFound("project");
}

const { subject, body, type, style } = TemplateSchemas.create.parse(req.body);
const { subject, body, type, style, email, from } = TemplateSchemas.create.parse(req.body);

if (email && !project.verified) {
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
}

if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
throw new NotAllowed("The sender address must be the same domain as the project's email address");
}

const template = await prisma.template.create({
data: {
Expand All @@ -110,6 +120,8 @@ export class Templates {
body,
type,
style,
from: from === "" ? null : from,
email: email === "" ? null : email,
},
});

Expand Down Expand Up @@ -151,17 +163,32 @@ export class Templates {
throw new NotFound("project");
}

const { id, subject, body, type, style } = TemplateSchemas.update.parse(req.body);
const { id, subject, body, type, style, email, from } = TemplateSchemas.update.parse(req.body);

let template = await TemplateService.id(id);

if (!template || template.projectId !== project.id) {
throw new NotFound("template");
}

if (email && !project.verified) {
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
}

if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
throw new NotAllowed("The sender address must be the same domain as the project's email address");
}

template = await prisma.template.update({
where: { id },
data: { subject, body, type, style },
data: {
subject,
body,
type,
style,
from: from === "" ? null : from,
email: email === "" ? null : email,
},
include: {
actions: true,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/services/ActionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ export class ActionService {

const { messageId } = await EmailService.send({
from: {
name: project.from ?? project.name,
email: project.verified && project.email ? project.email : "[email protected]",
name: action.template.from ?? project.from ?? project.name,
email: project.verified && project.email ? action.template.email ?? project.email : "[email protected]",
},
to: [contact.email],
content: {
Expand Down
55 changes: 48 additions & 7 deletions packages/dashboard/src/pages/campaigns/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { network } from "../../lib/network";
interface CampaignValues {
subject: string;
body: string;
email?: string;
from?: string;
recipients: string[];
style: "PLUNK" | "HTML";
}
Expand Down Expand Up @@ -71,6 +73,8 @@ export default function Index() {
watch,
reset,
setValue,
setError,
clearErrors,
} = useForm<CampaignValues>({
resolver: zodResolver(CampaignSchemas.update),
defaultValues: { recipients: [], body: undefined },
Expand All @@ -87,6 +91,21 @@ export default function Index() {
});
}, [reset, campaign]);

useEffect(() => {
watch((value, { name, type }) => {
if (name === "email") {
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
setError("email", {
type: "manual",
message: `The sender address must end with @${project.email?.split("@")[1]}`,
});
} else {
clearErrors("email");
}
}
});
}, [watch, project, setError, clearErrors]);

if (!project || !campaign || !events || (watch("body") as string | undefined) === undefined) {
return <FullscreenLoader />;
}
Expand Down Expand Up @@ -404,13 +423,35 @@ export default function Index() {
}
>
<form onSubmit={handleSubmit(update)} className="space-6 grid gap-6 sm:grid-cols-6">
<Input
className={"sm:col-span-6"}
label={"Subject"}
placeholder={`Welcome to ${project.name}!`}
register={register("subject")}
error={errors.subject}
/>
<div className={"sm:col-span-6 grid sm:grid-cols-6 gap-6"}>
<Input
className={"sm:col-span-6"}
label={"Subject"}
placeholder={`Welcome to ${project.name}!`}
register={register("subject")}
error={errors.subject}
/>

{project.verified && (
<Input
className={"sm:col-span-3"}
label={"Sender Email"}
placeholder={`${project.email}`}
register={register("email")}
error={errors.email}
/>
)}

{project.verified && (
<Input
className={"sm:col-span-3"}
label={"Sender Name"}
placeholder={`${project.name}`}
register={register("from")}
error={errors.from}
/>
)}
</div>

{contacts ? (
<>
Expand Down
58 changes: 50 additions & 8 deletions packages/dashboard/src/pages/campaigns/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import dayjs from "dayjs";
import { AnimatePresence, motion } from "framer-motion";
import { Search, Users2, XIcon } from "lucide-react";
import { useRouter } from "next/router";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { type FieldError, useForm } from "react-hook-form";
import { toast } from "sonner";
import { Alert, Card, Dropdown, Editor, FullscreenLoader, Input, MultiselectDropdown } from "../../components";
Expand All @@ -20,6 +20,8 @@ import { network } from "../../lib/network";
interface CampaignValues {
subject: string;
body: string;
email?: string;
from?: string;
recipients: string[];
style: "PLUNK" | "HTML";
}
Expand Down Expand Up @@ -58,6 +60,8 @@ export default function Index() {
formState: { errors },
setValue,
watch,
setError,
clearErrors,
} = useForm<CampaignValues>({
resolver: zodResolver(CampaignSchemas.create),
defaultValues: {
Expand All @@ -67,6 +71,21 @@ export default function Index() {
},
});

useEffect(() => {
watch((value, { name, type }) => {
if (name === "email") {
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
setError("email", {
type: "manual",
message: `The sender address must end with @${project.email?.split("@")[1]}`,
});
} else {
clearErrors("email");
}
}
});
}, [watch, project, setError, clearErrors]);

if (!project || !events) {
return <FullscreenLoader />;
}
Expand Down Expand Up @@ -194,13 +213,36 @@ export default function Index() {
<Dashboard>
<Card title={"Create a new campaign"}>
<form onSubmit={handleSubmit(create)} className="space-6 grid gap-6 sm:grid-cols-6">
<Input
className={"sm:col-span-6"}
label={"Subject"}
placeholder={`Welcome to ${project.name}!`}
register={register("subject")}
error={errors.subject}
/>
<div className={"sm:col-span-6 grid sm:grid-cols-6 gap-6"}>
<Input
className={"sm:col-span-6"}
label={"Subject"}
placeholder={`Welcome to ${project.name}!`}
register={register("subject")}
error={errors.subject}
/>

{project.verified && (
<Input
className={"sm:col-span-3"}
label={"Sender Email"}
placeholder={`${project.email}`}
register={register("email")}
error={errors.email}
/>
)}

{project.verified && (
<Input
className={"sm:col-span-3"}
label={"Sender Name"}
placeholder={`${project.name}`}
register={register("from")}
error={errors.from}
/>
)}
</div>

{contacts ? (
<>
<div className={"sm:col-span-3"}>
Expand Down
Loading

0 comments on commit a4b5069

Please sign in to comment.