Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f1d4d50
Feat: Added ability to overwrite sender mail and name for templates a…
driaug Sep 24, 2024
111e054
Fix: Correctly duplicate from
driaug Sep 24, 2024
e2d0d0e
Fix: Added Migration
driaug Sep 24, 2024
a4b5069
Merge pull request #95 from useplunk/dev-driaug-sender-details-overwrite
driaug Sep 24, 2024
d4bdcbf
Fix: Show project `from` and `name` as backup
driaug Sep 24, 2024
05463d0
Merge pull request #96 from useplunk/dev-driaug-sender-details-overwrite
driaug Sep 24, 2024
c96a007
Fix: Added try/catch to CRON
driaug Sep 25, 2024
b8da45b
Merge pull request #97 from useplunk/90-plunk-crashes-after-a-few-day…
driaug Sep 25, 2024
bad18ae
Fix: Check if APP_URI has https
driaug Sep 28, 2024
d105f7c
Merge pull request #100 from useplunk/dev-driaug-https-check
driaug Sep 28, 2024
daa8285
Fix: Add fixed yarn version
driaug Sep 28, 2024
7cf6cbb
Merge pull request #106 from useplunk/dev-driaug-yarn-fix
driaug Sep 28, 2024
ff5c6af
Fix: Add fixed yarn version to Docker build
driaug Sep 28, 2024
8124773
Merge pull request #107 from useplunk/dev-driaug-yarn-fix
driaug Sep 28, 2024
6d367fd
Fixed error where track api route was not unsubscribing contacts
ejscheepers Oct 15, 2024
1755890
Removed breaking change + bumped version
ejscheepers Oct 15, 2024
a0e119e
Fixed case where subscried is null, defaulting to true
ejscheepers Oct 15, 2024
52f4a3c
Made change as suggested
ejscheepers Oct 15, 2024
1126c18
Merge pull request #113 from ejscheepers/main
driaug Oct 15, 2024
4bb38b9
fix: incorrect domain verification records
nklmantey Oct 28, 2024
2ac6538
fixes spacing
nklmantey Oct 28, 2024
5b6d7fe
bumps up version number in package.json
nklmantey Oct 28, 2024
e3bda0d
updates replace variables deployment script
nklmantey Oct 28, 2024
6196c41
improves script
nklmantey Oct 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.env
.yarn/
.next/
.github/
dist/
Expand Down
894 changes: 894 additions & 0 deletions .yarn/releases/yarn-4.3.0.cjs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.3.0.cjs
9 changes: 7 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ Support can be asked in the `#contributions` channel of the [Plunk Discord serve

- Docker needs to be [installed](https://docs.docker.com/engine/install/) on your system.

### 2. Set your environment variables
### 2. Install dependencies

- Run `yarn install` to install the dependencies.

### 3. Set your environment variables

- Copy the `.env.example` files in the `api`, `dashboard` and `prisma` folder to `.env` in their respective folders.
- Set AWS credentials in the `api` `.env` file.

### 3. Start resources
### 4. Start resources

- Run `yarn services:up` to start a local database and a local redis server.
- Run `yarn migrate` to apply the migrations to the database.
Expand Down
26 changes: 21 additions & 5 deletions deployment/replace-variables.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,26 @@ if [ -z "${API_URI}" ]; then
exit 1
fi

# Find and replace baked values with real values for the API_URI
find /app/packages/dashboard/public /app/packages/dashboard/.next -type f -name "*.js" |
while read file; do
sed -i "s|PLUNK_API_URI|${API_URI}|g" "$file"
if [ -z "${AWS_REGION}" ]; then
echo "AWS_REGION is not set. Exiting..."
exit 1
fi

# Process each directory that might contain JS files
for dir in "/app/packages/dashboard/public" "/app/packages/dashboard/.next"; do
if [ -d "$dir" ]; then
# Find all JS files and process them
find "$dir" -type f -name "*.js" -o -name "*.mjs" | while read -r file; do
if [ -f "$file" ]; then
# Replace environment variables
sed -i "s|PLUNK_API_URI|${API_URI}|g" "$file"
sed -i "s|PLUNK_AWS_REGION|${AWS_REGION}|g" "$file"
echo "Processed: $file"
fi
done
else
echo "Warning: Directory $dir does not exist, skipping..."
fi
done

echo "Environment Variables Baked."
echo "Environment Variables Baked."
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"name": "plunk",
"version": "1.0.5",
"version": "1.0.9",
"private": true,
"license": "agpl-3.0",
"workspaces": {
"packages": ["packages/*"]
"packages": [
"packages/*"
]
},
"engines": {
"npm": ">=6.14.x",
"yarn": "1.22.x",
"yarn": "4.3.x",
"node": ">=18.x"
},
"devDependencies": {
Expand All @@ -34,5 +36,6 @@
"generate": "prisma generate",
"services:up": "docker compose -f docker-compose.dev.yml up -d",
"services:down": "docker compose -f docker-compose.dev.yml down"
}
},
"packageManager": "yarn@4.3.0"
}
2 changes: 1 addition & 1 deletion packages/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ENV
JWT_SECRET=mysupersecretJWTsecret
REDIS_URL=redis://127.0.0.1:56379
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_URL=postgresql://postgres:postgres@localhost:55432/postgres
DISABLE_SIGNUPS=false

# AWS
Expand Down
22 changes: 16 additions & 6 deletions packages/api/src/app/cron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ import { API_URI } from "./constants";

export const task = cron.schedule("* * * * *", () => {
signale.info("Running scheduled tasks");
void fetch(`${API_URI}/tasks`, {
method: "POST",
});
try {
void fetch(`${API_URI}/tasks`, {
method: "POST",
});
} catch (e) {
signale.error("Failed to run scheduled tasks. Please check the error below");
console.error(e);
}

signale.info("Updating verified identities");
void fetch(`${API_URI}/identities/update`, {
method: "POST",
});
try {
void fetch(`${API_URI}/identities/update`, {
method: "POST",
});
} catch (e) {
signale.error("Failed to update verified identities. Please check the error below");
console.error(e);
}
});
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 : "no-reply@useplunk.dev";
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 : "no-reply@useplunk.dev";
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 : "no-reply@useplunk.dev",
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
11 changes: 4 additions & 7 deletions packages/api/src/controllers/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,12 @@ export class V1 {
redis.del(Keys.Contact.id(contact.id));
redis.del(Keys.Contact.email(project.id, contact.email));
} else {
if (subscribed && contact.subscribed !== subscribed) {
contact = await prisma.contact.update({
where: { id: contact.id },
data: { subscribed },
});

if (subscribed !== null && contact.subscribed !== subscribed) {
contact = await prisma.contact.update({where: {id: contact.id}, data: {subscribed}});

redis.del(Keys.Contact.id(contact.id));
redis.del(Keys.Contact.email(project.id, contact.email));
}
}
}

if (data) {
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 : "no-reply@useplunk.dev",
name: action.template.from ?? project.from ?? project.name,
email: project.verified && project.email ? action.template.email ?? project.email : "no-reply@useplunk.dev",
},
to: [contact.email],
content: {
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/services/EmailService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ ${
<hr style="border: none; border-top: 1px solid #eaeaea; width: 100%; margin-top: 12px; margin-bottom: 12px;">
<p style="font-size: 12px; line-height: 24px; margin: 16px 0; text-align: center; color: rgb(64, 64, 64);">
You received this email because you agreed to receive emails from ${project.name}. If you no longer wish to receive emails like this, please
<a href="https://${APP_URI}/unsubscribe/${contact.id}">update your preferences</a>.
<a href="${APP_URI.startsWith("https://") ? APP_URI : `https://${APP_URI}`}/unsubscribe/${contact.id}">update your preferences</a>.
</p>
</td>
</tr>
Expand Down Expand Up @@ -472,7 +472,7 @@ ${
<mj-divider border-width="2px" border-color="#f5f5f5"></mj-divider>
<mj-text align="center">
<p style="color: #a3a3a3; text-decoration: none; font-size: 12px; line-height: 1.7142857;">
You received this email because you agreed to receive emails from ${project.name}. If you no longer wish to receive emails like this, please <a style="text-decoration: underline" href="${APP_URI}/unsubscribe/${contact.id}" target="_blank">update your preferences</a>.
You received this email because you agreed to receive emails from ${project.name}. If you no longer wish to receive emails like this, please <a style="text-decoration: underline" href="${APP_URI.startsWith("https://") ? APP_URI : `https://${APP_URI}`}/unsubscribe/${contact.id}" target="_blank">update your preferences</a>.
</p>
</mj-text>
`
Expand Down
Loading