🛂 Reset isQuarantined on the first of month
This commit is contained in:
@ -13,6 +13,7 @@ import { SendMailOptions } from 'nodemailer'
|
||||
import { sendEmail } from '../sendEmail'
|
||||
|
||||
type AlmostReachedChatsLimitEmailProps = {
|
||||
usagePercent: number
|
||||
chatsLimit: number
|
||||
url: string
|
||||
}
|
||||
@ -26,6 +27,7 @@ const readableResetDate = firstDayOfNextMonth
|
||||
.join(' ')
|
||||
|
||||
export const AlmostReachedChatsLimitEmail = ({
|
||||
usagePercent,
|
||||
chatsLimit,
|
||||
url,
|
||||
}: AlmostReachedChatsLimitEmailProps) => {
|
||||
@ -45,7 +47,8 @@ export const AlmostReachedChatsLimitEmail = ({
|
||||
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
||||
<Text>
|
||||
This means you've almost reached your monthly chats limit.
|
||||
You currently reached 80% of {readableChatsLimit} chats.
|
||||
You currently reached {usagePercent}% of {readableChatsLimit}{' '}
|
||||
chats.
|
||||
</Text>
|
||||
<Text>This limit will be reset on {readableResetDate}.</Text>
|
||||
<Text fontWeight="800">
|
||||
|
@ -1,65 +0,0 @@
|
||||
import React, { ComponentProps } from 'react'
|
||||
import {
|
||||
Mjml,
|
||||
MjmlBody,
|
||||
MjmlSection,
|
||||
MjmlColumn,
|
||||
MjmlSpacer,
|
||||
} from '@faire/mjml-react'
|
||||
import { render } from '@faire/mjml-react/utils/render'
|
||||
import { Button, Head, HeroImage, Text } from '../components'
|
||||
import { SendMailOptions } from 'nodemailer'
|
||||
import { sendEmail } from '../sendEmail'
|
||||
|
||||
type AlmostReachedStorageLimitEmailProps = {
|
||||
storageLimit: number
|
||||
url: string
|
||||
}
|
||||
|
||||
export const AlmostReachedStorageLimitEmail = ({
|
||||
storageLimit,
|
||||
url,
|
||||
}: AlmostReachedStorageLimitEmailProps) => {
|
||||
const readableStorageLimit = `${storageLimit} GB`
|
||||
|
||||
return (
|
||||
<Mjml>
|
||||
<Head />
|
||||
<MjmlBody width={600}>
|
||||
<MjmlSection padding="0">
|
||||
<MjmlColumn>
|
||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/yourBotIsFlyingEmailBanner.png" />
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
||||
<Text>
|
||||
This means you've almost reached your storage limit. You
|
||||
currently reached 80% of your {readableStorageLimit} storage
|
||||
limit.
|
||||
</Text>
|
||||
<Text fontWeight="800">
|
||||
Upon this limit your bots will still continue to collect new
|
||||
files, but we ask you kindly to upgrade your storage limit or
|
||||
delete existing results to free up space.
|
||||
</Text>
|
||||
<MjmlSpacer height="24px" />
|
||||
<Button link={url}>Upgrade workspace</Button>
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
</MjmlBody>
|
||||
</Mjml>
|
||||
)
|
||||
}
|
||||
|
||||
export const sendAlmostReachedStorageLimitEmail = ({
|
||||
to,
|
||||
...props
|
||||
}: Pick<SendMailOptions, 'to'> &
|
||||
ComponentProps<typeof AlmostReachedStorageLimitEmail>) =>
|
||||
sendEmail({
|
||||
to,
|
||||
subject: "You're close to your storage limit",
|
||||
html: render(<AlmostReachedStorageLimitEmail {...props} />).html,
|
||||
})
|
@ -1,64 +0,0 @@
|
||||
import { ComponentProps } from 'react'
|
||||
import {
|
||||
Mjml,
|
||||
MjmlBody,
|
||||
MjmlSection,
|
||||
MjmlColumn,
|
||||
MjmlSpacer,
|
||||
} from '@faire/mjml-react'
|
||||
import { render } from '@faire/mjml-react/utils/render'
|
||||
import { Button, Head, HeroImage, Text } from '../components'
|
||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
||||
import { SendMailOptions } from 'nodemailer'
|
||||
import { sendEmail } from '../sendEmail'
|
||||
|
||||
type ReachedChatsLimitEmailProps = {
|
||||
chatsLimit: number
|
||||
url: string
|
||||
}
|
||||
|
||||
export const ReachedChatsLimitEmail = ({
|
||||
chatsLimit,
|
||||
url,
|
||||
}: ReachedChatsLimitEmailProps) => {
|
||||
const readableChatsLimit = parseNumberWithCommas(chatsLimit)
|
||||
|
||||
return (
|
||||
<Mjml>
|
||||
<Head />
|
||||
<MjmlBody width={600}>
|
||||
<MjmlSection padding="0">
|
||||
<MjmlColumn>
|
||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" />
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>
|
||||
It just happened, you've reached your monthly{' '}
|
||||
{readableChatsLimit} chats limit 😮
|
||||
</Text>
|
||||
<Text>
|
||||
If you'd like your bots to continue chatting with your users
|
||||
this month, then you need to upgrade your plan. 🚀
|
||||
</Text>
|
||||
|
||||
<MjmlSpacer height="24px" />
|
||||
<Button link={url}>Upgrade workspace</Button>
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
</MjmlBody>
|
||||
</Mjml>
|
||||
)
|
||||
}
|
||||
|
||||
export const sendReachedChatsLimitEmail = ({
|
||||
to,
|
||||
...props
|
||||
}: Pick<SendMailOptions, 'to'> &
|
||||
ComponentProps<typeof ReachedChatsLimitEmail>) =>
|
||||
sendEmail({
|
||||
to,
|
||||
subject: "You've reached your chats limit",
|
||||
html: render(<ReachedChatsLimitEmail {...props} />).html,
|
||||
})
|
@ -1,63 +0,0 @@
|
||||
import React, { ComponentProps } from 'react'
|
||||
import {
|
||||
Mjml,
|
||||
MjmlBody,
|
||||
MjmlSection,
|
||||
MjmlColumn,
|
||||
MjmlSpacer,
|
||||
} from '@faire/mjml-react'
|
||||
import { render } from '@faire/mjml-react/utils/render'
|
||||
import { Button, Head, HeroImage, Text } from '../components'
|
||||
import { SendMailOptions } from 'nodemailer'
|
||||
import { sendEmail } from '../sendEmail'
|
||||
|
||||
type ReachedStorageLimitEmailProps = {
|
||||
storageLimit: number
|
||||
url: string
|
||||
}
|
||||
|
||||
export const ReachedStorageLimitEmail = ({
|
||||
storageLimit,
|
||||
url,
|
||||
}: ReachedStorageLimitEmailProps) => {
|
||||
const readableStorageLimit = `${storageLimit} GB`
|
||||
|
||||
return (
|
||||
<Mjml>
|
||||
<Head />
|
||||
<MjmlBody width={600}>
|
||||
<MjmlSection padding="0">
|
||||
<MjmlColumn>
|
||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" />
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
||||
<MjmlColumn>
|
||||
<Text>
|
||||
It just happened, you've reached your {readableStorageLimit}{' '}
|
||||
storage limit 😮
|
||||
</Text>
|
||||
<Text>
|
||||
If you'd like to continue collecting files, then you need to
|
||||
upgrade your plan or remove existing results to free up space. 🚀
|
||||
</Text>
|
||||
|
||||
<MjmlSpacer height="24px" />
|
||||
<Button link={url}>Upgrade workspace</Button>
|
||||
</MjmlColumn>
|
||||
</MjmlSection>
|
||||
</MjmlBody>
|
||||
</Mjml>
|
||||
)
|
||||
}
|
||||
|
||||
export const sendReachedStorageLimitEmail = ({
|
||||
to,
|
||||
...props
|
||||
}: Pick<SendMailOptions, 'to'> &
|
||||
ComponentProps<typeof ReachedStorageLimitEmail>) =>
|
||||
sendEmail({
|
||||
to,
|
||||
subject: "You've reached your storage limit",
|
||||
html: render(<ReachedStorageLimitEmail {...props} />).html,
|
||||
})
|
@ -1,8 +1,5 @@
|
||||
export * from './AlmostReachedChatsLimitEmail'
|
||||
export * from './AlmostReachedStorageLimitEmail'
|
||||
export * from './DefaultBotNotificationEmail'
|
||||
export * from './GuestInvitationEmail'
|
||||
export * from './ReachedChatsLimitEmail'
|
||||
export * from './ReachedStorageLimitEmail'
|
||||
export * from './WorkspaceMemberInvitationEmail'
|
||||
export * from './MagicLinkEmail'
|
||||
|
@ -3,11 +3,8 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import {
|
||||
AlmostReachedChatsLimitEmail,
|
||||
AlmostReachedStorageLimitEmail,
|
||||
DefaultBotNotificationEmail,
|
||||
GuestInvitationEmail,
|
||||
ReachedChatsLimitEmail,
|
||||
ReachedStorageLimitEmail,
|
||||
WorkspaceMemberInvitation,
|
||||
} from './emails'
|
||||
import { MagicLinkEmail } from './emails/MagicLinkEmail'
|
||||
@ -47,38 +44,12 @@ const createHtmlFile = () => {
|
||||
path.resolve(__dirname, 'dist', 'almostReachedChatsLimit.html'),
|
||||
render(
|
||||
<AlmostReachedChatsLimitEmail
|
||||
usagePercent={86}
|
||||
url={'https://app.typebot.io'}
|
||||
chatsLimit={2000}
|
||||
/>
|
||||
).html
|
||||
)
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, 'dist', 'almostReachedStorageLimit.html'),
|
||||
render(
|
||||
<AlmostReachedStorageLimitEmail
|
||||
url={'https://app.typebot.io'}
|
||||
storageLimit={4}
|
||||
/>
|
||||
).html
|
||||
)
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, 'dist', 'reachedChatsLimit.html'),
|
||||
render(
|
||||
<ReachedChatsLimitEmail
|
||||
url={'https://app.typebot.io'}
|
||||
chatsLimit={10000}
|
||||
/>
|
||||
).html
|
||||
)
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, 'dist', 'reachedStorageLimit.html'),
|
||||
render(
|
||||
<ReachedStorageLimitEmail
|
||||
url={'https://app.typebot.io'}
|
||||
storageLimit={8}
|
||||
/>
|
||||
).html
|
||||
)
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, 'dist', 'defaultBotNotification.html'),
|
||||
render(
|
||||
|
@ -14,6 +14,7 @@ export const cleanDatabase = async () => {
|
||||
if (isFirstOfMonth) {
|
||||
await deleteArchivedResults()
|
||||
await deleteArchivedTypebots()
|
||||
await resetQuarantinedWorkspaces()
|
||||
}
|
||||
console.log('Done!')
|
||||
}
|
||||
@ -118,4 +119,14 @@ const deleteExpiredVerificationTokens = async () => {
|
||||
console.log(`Deleted ${count} expired verifiations tokens.`)
|
||||
}
|
||||
|
||||
const resetQuarantinedWorkspaces = async () =>
|
||||
prisma.workspace.updateMany({
|
||||
where: {
|
||||
isQuarantined: true,
|
||||
},
|
||||
data: {
|
||||
isQuarantined: false,
|
||||
},
|
||||
})
|
||||
|
||||
cleanDatabase().then()
|
||||
|
@ -11,6 +11,22 @@ import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEven
|
||||
import { Workspace } from '@typebot.io/schemas'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||
|
||||
type WorkspaceForDigest = Pick<
|
||||
Workspace,
|
||||
| 'id'
|
||||
| 'plan'
|
||||
| 'customChatsLimit'
|
||||
| 'customStorageLimit'
|
||||
| 'additionalChatsIndex'
|
||||
| 'additionalStorageIndex'
|
||||
| 'isQuarantined'
|
||||
> & {
|
||||
members: (Pick<MemberInWorkspace, 'role'> & {
|
||||
user: { id: string; email: string | null }
|
||||
})[]
|
||||
}
|
||||
|
||||
export const sendTotalResultsDigest = async () => {
|
||||
await promptAndSetEnvironment('production')
|
||||
@ -53,12 +69,15 @@ export const sendTotalResultsDigest = async () => {
|
||||
select: {
|
||||
id: true,
|
||||
typebots: { select: { id: true } },
|
||||
members: { select: { userId: true, role: true } },
|
||||
members: {
|
||||
select: { user: { select: { id: true, email: true } }, role: true },
|
||||
},
|
||||
additionalChatsIndex: true,
|
||||
additionalStorageIndex: true,
|
||||
customChatsLimit: true,
|
||||
customStorageLimit: true,
|
||||
plan: true,
|
||||
isQuarantined: true,
|
||||
},
|
||||
})
|
||||
|
||||
@ -71,7 +90,7 @@ export const sendTotalResultsDigest = async () => {
|
||||
return workspace.members
|
||||
.filter((member) => member.role !== WorkspaceRole.GUEST)
|
||||
.map((member, memberIndex) => ({
|
||||
userId: member.userId,
|
||||
userId: member.user.id,
|
||||
workspace: workspace,
|
||||
typebotId: result.typebotId,
|
||||
totalResultsYesterday: result._count._all,
|
||||
@ -115,20 +134,13 @@ export const sendTotalResultsDigest = async () => {
|
||||
}
|
||||
|
||||
const sendAlertIfLimitReached = async (
|
||||
workspaces: (Pick<
|
||||
Workspace,
|
||||
| 'id'
|
||||
| 'plan'
|
||||
| 'customChatsLimit'
|
||||
| 'customStorageLimit'
|
||||
| 'additionalChatsIndex'
|
||||
| 'additionalStorageIndex'
|
||||
> & { members: Pick<MemberInWorkspace, 'userId' | 'role'>[] })[]
|
||||
workspaces: WorkspaceForDigest[]
|
||||
): Promise<TelemetryEvent[]> => {
|
||||
const events: TelemetryEvent[] = []
|
||||
const taggedWorkspaces: string[] = []
|
||||
for (const workspace of workspaces) {
|
||||
if (taggedWorkspaces.includes(workspace.id)) continue
|
||||
if (taggedWorkspaces.includes(workspace.id) || workspace.isQuarantined)
|
||||
continue
|
||||
taggedWorkspaces.push(workspace.id)
|
||||
const { totalChatsUsed, totalStorageUsed } = await getUsage(workspace.id)
|
||||
const totalStorageUsedInGb = totalStorageUsed / 1024 / 1024 / 1024
|
||||
@ -145,7 +157,7 @@ const sendAlertIfLimitReached = async (
|
||||
(member) =>
|
||||
({
|
||||
name: 'Workspace limit reached',
|
||||
userId: member.userId,
|
||||
userId: member.user.id,
|
||||
workspaceId: workspace.id,
|
||||
data: {
|
||||
totalChatsUsed,
|
||||
@ -156,7 +168,20 @@ const sendAlertIfLimitReached = async (
|
||||
} satisfies TelemetryEvent)
|
||||
)
|
||||
)
|
||||
continue
|
||||
}
|
||||
// if (
|
||||
// chatsLimit > 0 &&
|
||||
// totalChatsUsed >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT
|
||||
// )
|
||||
// await sendAlmostReachedChatsLimitEmail({
|
||||
// to: workspace.members
|
||||
// .map((member) => member.user.email)
|
||||
// .filter(isDefined),
|
||||
// usagePercent: Math.round((totalChatsUsed / chatsLimit) * 100),
|
||||
// chatsLimit,
|
||||
// url: `https://app.typebot.io/typebots?workspaceId=${workspace.id}`,
|
||||
// })
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
Reference in New Issue
Block a user