🧑💻 (emails) Add decent emails management
Use mjml-react to generate emails. Put all emails in a independent package.
This commit is contained in:
@ -1,31 +1,16 @@
|
||||
import Document, {
|
||||
Html,
|
||||
Head,
|
||||
Main,
|
||||
NextScript,
|
||||
DocumentContext,
|
||||
} from 'next/document'
|
||||
/* eslint-disable @next/next/no-sync-scripts */
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const initialProps = await Document.getInitialProps(ctx)
|
||||
return { ...initialProps }
|
||||
}
|
||||
const Document = () => (
|
||||
<Html>
|
||||
<Head>
|
||||
<script src="/__env.js" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||
<script src="/__env.js" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
||||
export default Document
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { almostReachedStorageLimitEmail } from 'assets/emails/almostReachedStorageLimitEmail'
|
||||
import { reachedStorageLimitEmail } from 'assets/emails/reachedStorageLimitEmail'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { InputBlockType, PublicTypebot } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
badRequest,
|
||||
generatePresignedUrl,
|
||||
methodNotAllowed,
|
||||
sendEmailNotification,
|
||||
} from 'utils/api'
|
||||
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
||||
import { byId, getStorageLimit, isDefined, env } from 'utils'
|
||||
import {
|
||||
sendAlmostReachedStorageLimitEmail,
|
||||
sendReachedStorageLimitEmail,
|
||||
} from 'emails'
|
||||
|
||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||
|
||||
@ -60,6 +57,7 @@ const handler = async (
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const checkStorageLimit = async (typebotId: string) => {
|
||||
const typebot = await prisma.typebot.findFirst({
|
||||
where: { id: typebotId },
|
||||
@ -102,7 +100,7 @@ const checkStorageLimit = async (typebotId: string) => {
|
||||
!hasSentFirstEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendAlmostReachStorageLimitEmail({
|
||||
await sendAlmostReachStorageLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
storageLimit,
|
||||
})
|
||||
@ -111,14 +109,14 @@ const checkStorageLimit = async (typebotId: string) => {
|
||||
!hasSentSecondEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendReachStorageLimitEmail({
|
||||
await sendReachStorageLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
storageLimit,
|
||||
})
|
||||
return totalStorageUsed >= storageLimitBytes
|
||||
}
|
||||
|
||||
const sendAlmostReachStorageLimitEmail = async ({
|
||||
const sendAlmostReachStorageLimitNotification = async ({
|
||||
workspaceId,
|
||||
storageLimit,
|
||||
}: {
|
||||
@ -129,22 +127,20 @@ const sendAlmostReachStorageLimitEmail = async ({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
const readableStorageLimit = `${storageLimit}GB`
|
||||
await sendEmailNotification({
|
||||
|
||||
await sendAlmostReachedStorageLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
subject: "You're close to your storage limit",
|
||||
html: almostReachedStorageLimitEmail({
|
||||
readableStorageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
}),
|
||||
storageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { storageLimitFirstEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
|
||||
const sendReachStorageLimitEmail = async ({
|
||||
const sendReachStorageLimitNotification = async ({
|
||||
workspaceId,
|
||||
storageLimit,
|
||||
}: {
|
||||
@ -155,15 +151,13 @@ const sendReachStorageLimitEmail = async ({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
const readableStorageLimit = `${storageLimit}GB`
|
||||
await sendEmailNotification({
|
||||
|
||||
await sendReachedStorageLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
subject: "You've hit your storage limit",
|
||||
html: reachedStorageLimitEmail({
|
||||
readableStorageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
}),
|
||||
storageLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { storageLimitSecondEmailSentAt: new Date() },
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
saveSuccessLog,
|
||||
} from 'services/api/utils'
|
||||
import Mail from 'nodemailer/lib/mailer'
|
||||
import { newLeadEmailContent } from 'assets/emails/newLeadEmailContent'
|
||||
import { DefaultBotNotificationEmail, render } from 'emails'
|
||||
|
||||
const cors = initMiddleware(Cors())
|
||||
|
||||
@ -84,6 +84,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
typebotId,
|
||||
resultValues,
|
||||
})
|
||||
|
||||
if (!emailBody) {
|
||||
await saveErrorLog(resultId, 'Email not sent', {
|
||||
transportConfig,
|
||||
@ -121,6 +122,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await saveErrorLog(resultId, 'Email not sent', {
|
||||
transportConfig,
|
||||
email,
|
||||
error: err,
|
||||
})
|
||||
return res.status(500).send({
|
||||
message: `Email not sent. Error: ${err}`,
|
||||
@ -177,10 +179,12 @@ const getEmailBody = async ({
|
||||
],
|
||||
})(resultValues)
|
||||
return {
|
||||
html: newLeadEmailContent(
|
||||
`${process.env.NEXTAUTH_URL}/typebots/${typebot.id}/results`,
|
||||
omit(answers, 'submittedAt')
|
||||
),
|
||||
html: render(
|
||||
<DefaultBotNotificationEmail
|
||||
resultsUrl={`${process.env.NEXTAUTH_URL}/typebots/${typebot.id}/results`}
|
||||
answers={omit(answers, 'submittedAt')}
|
||||
/>
|
||||
).html,
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { almostReachedChatsLimitEmail } from 'assets/emails/almostReachedChatsLimitEmail'
|
||||
import { reachedSChatsLimitEmail } from 'assets/emails/reachedChatsLimitEmail'
|
||||
import { Workspace, WorkspaceRole } from 'db'
|
||||
import {
|
||||
sendAlmostReachedChatsLimitEmail,
|
||||
sendReachedChatsLimitEmail,
|
||||
} from 'emails'
|
||||
import prisma from 'libs/prisma'
|
||||
import { ResultWithAnswers } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { env, getChatsLimit, isDefined, parseNumberWithCommas } from 'utils'
|
||||
import { sendEmailNotification, methodNotAllowed } from 'utils/api'
|
||||
import { env, getChatsLimit, isDefined } from 'utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||
|
||||
@ -60,6 +62,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const checkChatsUsage = async (
|
||||
workspace: Pick<
|
||||
Workspace,
|
||||
@ -96,87 +99,64 @@ const checkChatsUsage = async (
|
||||
!hasSentFirstEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendAlmostReachChatsLimitEmail({
|
||||
await sendAlmostReachChatsLimitNotification({
|
||||
workspaceId: workspace.id,
|
||||
chatLimit: chatsLimit,
|
||||
firstDayOfNextMonth,
|
||||
chatsLimit,
|
||||
})
|
||||
if (
|
||||
chatsCount >= chatsLimit &&
|
||||
!hasSentSecondEmail &&
|
||||
env('E2E_TEST') !== 'true'
|
||||
)
|
||||
await sendReachedAlertEmail({
|
||||
await sendReachedAlertNotification({
|
||||
workspaceId: workspace.id,
|
||||
chatLimit: chatsLimit,
|
||||
firstDayOfNextMonth,
|
||||
chatsLimit,
|
||||
})
|
||||
return chatsCount >= chatsLimit
|
||||
}
|
||||
|
||||
const sendAlmostReachChatsLimitEmail = async ({
|
||||
const sendAlmostReachChatsLimitNotification = async ({
|
||||
workspaceId,
|
||||
chatLimit,
|
||||
firstDayOfNextMonth,
|
||||
chatsLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
chatLimit: number
|
||||
firstDayOfNextMonth: Date
|
||||
chatsLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
const readableChatsLimit = parseNumberWithCommas(chatLimit)
|
||||
const readableResetDate = firstDayOfNextMonth
|
||||
.toDateString()
|
||||
.split(' ')
|
||||
.slice(1, 4)
|
||||
.join(' ')
|
||||
|
||||
await sendEmailNotification({
|
||||
await sendAlmostReachedChatsLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
subject: "You're close to your chats limit",
|
||||
html: almostReachedChatsLimitEmail({
|
||||
readableChatsLimit,
|
||||
readableResetDate,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
}),
|
||||
chatsLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { chatsLimitFirstEmailSentAt: new Date() },
|
||||
})
|
||||
}
|
||||
|
||||
const sendReachedAlertEmail = async ({
|
||||
const sendReachedAlertNotification = async ({
|
||||
workspaceId,
|
||||
chatLimit,
|
||||
firstDayOfNextMonth,
|
||||
chatsLimit,
|
||||
}: {
|
||||
workspaceId: string
|
||||
chatLimit: number
|
||||
firstDayOfNextMonth: Date
|
||||
chatsLimit: number
|
||||
}) => {
|
||||
const members = await prisma.memberInWorkspace.findMany({
|
||||
where: { role: WorkspaceRole.ADMIN, workspaceId },
|
||||
include: { user: { select: { email: true } } },
|
||||
})
|
||||
const readableChatsLimit = parseNumberWithCommas(chatLimit)
|
||||
const readableResetDate = firstDayOfNextMonth
|
||||
.toDateString()
|
||||
.split(' ')
|
||||
.slice(1, 4)
|
||||
.join(' ')
|
||||
await sendEmailNotification({
|
||||
|
||||
await sendReachedChatsLimitEmail({
|
||||
to: members.map((member) => member.user.email).filter(isDefined),
|
||||
subject: "You've hit your monthly chats limit",
|
||||
html: reachedSChatsLimitEmail({
|
||||
readableChatsLimit,
|
||||
readableResetDate,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
}),
|
||||
chatsLimit,
|
||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
|
||||
})
|
||||
|
||||
await prisma.workspace.update({
|
||||
where: { id: workspaceId },
|
||||
data: { chatsLimitSecondEmailSentAt: new Date() },
|
||||
|
Reference in New Issue
Block a user