Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 0 additions & 29 deletions packages/emails/hotfix/monitor-alert.ts

This file was deleted.

55 changes: 55 additions & 0 deletions packages/emails/raw/_components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export function renderTemplate(children: string, title: string) {
return `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>${title}</title>
<style>
body {
font-family: Arial, sans-serif;
}
hr {
border: none;
width: 100%;
border-top: 1px solid #eaeaea;
}
</style>
</head>
<body>
${children}
</body>
</html>
`;
}

export function renderTemplateWithFooter(children: string, title: string) {
return `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>${title}</title>
<style>
body {
font-family: Arial, sans-serif;
}
hr {
border: none;
width: 100%;
border-top: 1px solid #eaeaea;
}
</style>
</head>
<body>
${children}
<hr style="margin-top: 48px;">
<p>
OpenStatus ・ 122 Rue Amelot ・ 75011 Paris, France ・ <a target="_blank" rel="noopener noreferrer" href="mailto:ping@openstatus.dev">Contact Support</a>
</p>
</body>
</html>
`;
}
13 changes: 13 additions & 0 deletions packages/emails/raw/followup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { renderTemplate } from "./_components";

export function renderFollowUpEmail() {
return renderTemplate(
`
<p>Hello 👋</p>
<p>How's everything going with openstatus so far? Let me know if you run into any issues, or have any feedback, good or bad!</p>
<p>Thank you,</p>
<p>Thibault Le Ouay Ducasse</p>
`,
"How's it going with openstatus?",
);
}
49 changes: 49 additions & 0 deletions packages/emails/raw/monitor-alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { renderTemplateWithFooter } from "./_components";

interface MonitorAlertEmailProps {
name?: string;
type: "alert" | "degraded" | "recovery";
url?: string;
status?: string;
latency?: string;
region?: string;
timestamp?: string;
message?: string;
}

const config = {
alert: {
title: "down",
color: "red",
},
degraded: {
title: "degraded",
color: "yellow",
},
recovery: {
title: "up again",
color: "green",
},
};

export function renderMonitorAlertEmail(props: MonitorAlertEmailProps) {
return renderTemplateWithFooter(
`
<p><strong>Your monitor is <span style="color: ${config[props.type].color}">${
config[props.type].title
}</span></strong></p>
<ul>
<li>Endpoint: ${props.url || "N/A"}</li>
<li>Status: ${props.status || "N/A"}</li>
<li>Latency: ${props.latency || "N/A"}</li>
<li>Region: ${props.region || "N/A"}</li>
<li>Timestamp: ${props.timestamp || "N/A"}</li>
<li>Message: ${props.message || "N/A"}</li>
</ul>
<p>
<a href="https://app.openstatus.dev">Go to dashboard</a>
</p>
`,
`Your monitor ${props.name} is ${config[props.type]}`,
);
}
23 changes: 23 additions & 0 deletions packages/emails/raw/monitor-deactivation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { renderTemplateWithFooter } from "./_components";

interface MonitorDeactivationEmailProps {
deactivateAt: Date;
}

export function renderMonitorDeactivationEmail({
deactivateAt,
}: MonitorDeactivationEmailProps) {
return renderTemplateWithFooter(
`
<p>Hello 👋</p>
<p>To save on cloud resources and avoid having stale monitors. We are deactivating monitors for free account if you have not logged in for the last 2 months.</p>
<p>Your monitor(s) will be deactivated on <strong>${deactivateAt.toDateString()}</strong>.</p>
<p>If you would like to keep your monitor(s) active, please login to your account or upgrade to a paid plan.</p>
<p><a href="https://www.openstatus.dev/app">Login</a></p>
<p>If you have any questions, please reply to this email.</p>
<p>Thibault</p>
<p>Check out our latest update <a href="https://www.openstatus.dev/changelog?ref=paused-email">here</a></p>
`,
"Login to your OpenStatus account to keep your monitors active",
);
}
16 changes: 16 additions & 0 deletions packages/emails/raw/monitor-paused.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { renderTemplateWithFooter } from "./_components";

export function renderMonitorPausedEmail() {
return renderTemplateWithFooter(
`
<p>Hello 👋</p>
<p>To save on cloud resources, your monitor(s) has been paused due to inactivity.</p>
<p>If you would like to unpause your monitor(s), please login to your account or upgrade to a paid plan.</p>
<p><a href="https://www.openstatus.dev/app">Login</a></p>
<p>If you have any questions, please reply to this email.</p>
<p>Thibault</p>
<p>Check out our latest update <a href="https://www.openstatus.dev/changelog?ref=paused-email">here</a></p>
`,
"Your monitors have been paused",
);
}
28 changes: 28 additions & 0 deletions packages/emails/raw/page-subscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { renderTemplate } from "./_components";

interface PageSubscriptionEmailProps {
page: string;
link: string;
img?: {
src: string;
alt: string;
href: string;
};
}

export function renderPageSubscriptionEmail({
page,
link,
}: PageSubscriptionEmailProps) {
return renderTemplate(
`
<p>Hello 👋</p>
<p>You are receiving this email because you subscribed to receive updates from "${page}" Status Page.</p>
<p>To confirm your subscription, please click the link below. The link is valid for 7 days. If you believe this is a mistake, please ignore this email.</p>
<p>
<a href="${link}">Confirm subscription</a>
</p>
`,
`Confirm your subscription to "${page}" Status Page`,
);
}
1 change: 1 addition & 0 deletions packages/emails/raw/status-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// TODO: convert md to html
29 changes: 29 additions & 0 deletions packages/emails/raw/team-invitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { renderTemplateWithFooter } from "./_components";

const BASE_URL = "https://openstatus.dev/app/invite";

interface TeamInvitationEmailProps {
invitedBy: string;
workspaceName?: string | null;
token: string;
baseUrl?: string;
}

export function renderTeamInvitationEmail({
invitedBy,
workspaceName,
token,
baseUrl = BASE_URL,
}: TeamInvitationEmailProps) {
return renderTemplateWithFooter(
`
<p>Hello 👋</p>
<p>You have been invited to join ${
workspaceName || "openstatus.dev"
} by ${invitedBy}.</p>
<p>Click here to accept the invitation: <a href="${baseUrl}?token=${token}">accept invitation</a></p>
<p>If you don't have an account yet, it will require you to create one.</p>
`,
"You've been invited to join openstatus.dev",
);
}
22 changes: 22 additions & 0 deletions packages/emails/raw/welcome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { renderTemplate } from "./_components";

export function renderWelcomeEmail() {
return renderTemplate(
`
<p>Hello 👋</p>
<p>Welcome to openstatus</p>
<p>Openstatus is global uptime monitoring service with status page.</p>
<p>Here are a few things you can do with openstatus:</p>
<ul>
<li>Use our <a href="https://docs.openstatus.dev/cli/getting-started/?ref=email-onboarding">CLI</a> to create, update and trigger your monitors.</li>
<li>Learn how to monitor a <a href="https://docs.openstatus.dev/guides/how-to-monitor-mcp-server?ref=email-onboarding">MCP server</a>.</li>
<li>Explore our uptime monitoring as code <a href="https://github.com/openstatusHQ/cli-template/?ref=email-onboarding">template directory</a>.</li>
<li>Build your own status page with our <a href="https://api.openstatus.dev/v1">API</a> and host it where you want. Here's our <a href="https://github.com/openstatusHQ/astro-status-page">Astro template</a> that you can easily host on Cloudflare.</li>
</ul>
<p>Quick question: How did you learn about us? and why did you sign up?</p>
<p>Thank you,</p>
<p>Thibault Le Ouay Ducasse, co-founder of openstatus</p>
`,
"Welcome to openstatus",
);
}
17 changes: 8 additions & 9 deletions packages/emails/src/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import { render } from "@react-email/render";
import { Resend } from "resend";
import FollowUpEmail from "../emails/followup";
import type { MonitorAlertProps } from "../emails/monitor-alert";
import PageSubscriptionEmail from "../emails/page-subscription";
import type { PageSubscriptionProps } from "../emails/page-subscription";
import PageSubscriptionEmail from "../emails/page-subscription";
import StatusReportEmail from "../emails/status-report";
import type { StatusReportProps } from "../emails/status-report";
import TeamInvitationEmail from "../emails/team-invitation";
import type { TeamInvitationProps } from "../emails/team-invitation";
import { monitorAlertEmail } from "../hotfix/monitor-alert";
import { renderFollowUpEmail } from "../raw/followup";
import { renderMonitorAlertEmail } from "../raw/monitor-alert";
import { renderTeamInvitationEmail } from "../raw/team-invitation";

// split an array into chunks of a given size.
function chunk<T>(array: T[], size: number): T[][] {
Expand All @@ -35,7 +35,7 @@ export class EmailClient {
}

try {
const html = await render(<FollowUpEmail />);
const html = renderFollowUpEmail();
const result = await this.client.emails.send({
from: "Thibault Le Ouay Ducasse <welcome@openstatus.dev>",
replyTo: "Thibault Le Ouay Ducasse <thibault@openstatus.dev>",
Expand All @@ -61,7 +61,7 @@ export class EmailClient {
return;
}

const html = await render(<FollowUpEmail />);
const html = renderFollowUpEmail();
const result = await this.client.batch.send(
req.to.map((subscriber) => ({
from: "Thibault Le Ouay Ducasse <thibault@openstatus.dev>",
Expand Down Expand Up @@ -134,7 +134,7 @@ export class EmailClient {
}

try {
const html = await render(<TeamInvitationEmail {...req} />);
const html = renderTeamInvitationEmail(req);
const result = await this.client.emails.send({
from: `${
req.workspaceName ?? "OpenStatus"
Expand Down Expand Up @@ -164,8 +164,7 @@ export class EmailClient {
}

try {
// const html = await render(<MonitorAlertEmail {...req} />);
const html = monitorAlertEmail(req);
const html = renderMonitorAlertEmail(req);
const result = await this.client.emails.send({
from: "OpenStatus <notifications@notifications.openstatus.dev>",
subject: `${req.name}: ${req.type.toUpperCase()}`,
Expand Down