Skip to content

Commit

Permalink
feat: add more template
Browse files Browse the repository at this point in the history
  • Loading branch information
darkskygit committed Jan 7, 2025
1 parent 2d6018a commit cb96048
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 67 deletions.
122 changes: 63 additions & 59 deletions packages/backend/server/src/base/mailer/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
renderSetPasswordMail,
renderSignInMail,
renderSignUpMail,
renderTeamWorkspaceUpgradedMail,
renderVerifyChangeEmailMail,
renderVerifyEmailMail,
} from '../../mail-templates';
Expand Down Expand Up @@ -138,6 +139,32 @@ export class MailService {
});
}

// =================== Workspace Mails ===================

private extractWorkspaceInfo(ws: {
id: string;
name: string;
avatar: string;
}) {
const {
id: workspaceId,
name: workspaceName,
avatar: workspaceAvatar,
} = ws;
return {
workspaceId,
workspaceName,
attachments: [
{
cid: 'workspaceAvatar',
filename: 'image.png',
content: workspaceAvatar,
encoding: 'base64',
},
],
};
}

async sendMemberInviteMail(
to: string,
inviteId: string,
Expand All @@ -146,89 +173,66 @@ export class MailService {
user: { avatar: string; name: string };
}
) {
const {
user: { name: userName, avatar: userAvatar },
workspace: { name: workspaceName, avatar: workspaceAvatar },
} = invitationInfo;
const buttonUrl = this.url.link(`/invite/${inviteId}`);
const { name: userName, avatar: userAvatar } = invitationInfo.user;
const { workspaceName, attachments } = this.extractWorkspaceInfo(
invitationInfo.workspace
);
const { html, subject } = await renderMemberInviteMail({
userName,
userAvatar,
workspaceName,
url: buttonUrl,
});
return this.sendMail({
to,
subject,
html,
attachments: [
{
cid: 'workspaceAvatar',
filename: 'image.png',
content: workspaceAvatar,
encoding: 'base64',
},
],
url: this.url.link(`/invite/${inviteId}`),
});
return this.sendMail({ to, subject, html, attachments });
}

async sendMemberAcceptedEmail(
to: string,
props: { inviteeName: string; workspaceName: string }
props: {
inviteeName: string;
workspace: { id: string; name: string; avatar: string };
}
) {
const { html, subject } = await renderMemberAcceptedMail(props);

return this.sendMail({
to,
subject,
html,
const { workspaceName, attachments } = this.extractWorkspaceInfo(
props.workspace
);
const { html, subject } = await renderMemberAcceptedMail({
inviteeName: props.inviteeName,
workspaceName,
});
return this.sendMail({ to, subject, html, attachments });
}

async sendMemberLeaveEmail(
to: string,
props: { inviteeName: string; workspaceName: string }
props: {
inviteeName: string;
workspace: { id: string; name: string; avatar: string };
}
) {
const { html, subject } = await renderMemberLeaveMail(props);

return this.sendMail({
to,
subject,
html,
const { workspaceName, attachments } = this.extractWorkspaceInfo(
props.workspace
);
const { html, subject } = await renderMemberLeaveMail({
inviteeName: props.inviteeName,
workspaceName,
});
return this.sendMail({ to, subject, html, attachments });
}

// =================== Team Workspace Mails ===================
async sendTeamWorkspaceUpgradedEmail(
to: string,
ws: { id: string; name: string; isOwner: boolean }
ws: { id: string; name: string; avatar: string; isOwner: boolean }
) {
const { id: workspaceId, name: workspaceName, isOwner } = ws;

const baseContent = {
subject: `${workspaceName} has been upgraded to team workspace! 🎉`,
title: 'Welcome to the team workspace!',
content: `Great news! ${workspaceName} has been upgraded to team workspace by the workspace owner. You now have access to the following enhanced features:`,
};
if (isOwner) {
baseContent.subject =
'Your workspace has been upgraded to team workspace! 🎉';
baseContent.title = 'Welcome to the team workspace!';
baseContent.content = `${workspaceName} has been upgraded to team workspace with the following benefits:`;
}

const html = emailTemplate({
title: baseContent.title,
content: `${baseContent.content}
✓ 100 GB initial storage + 20 GB per seat
✓ 500 MB of maximum file size
✓ Unlimited team members (10+ seats)
✓ Multiple admin roles
✓ Priority customer support`,
buttonContent: 'Open Workspace',
buttonUrl: this.url.link(`/workspace/${workspaceId}`),
const { workspaceId, workspaceName, attachments } =
this.extractWorkspaceInfo(ws);
const { html, subject } = await renderTeamWorkspaceUpgradedMail({
url: this.url.link(`/workspace/${workspaceId}`),
workspaceName,
isOwner: ws.isOwner,
});
return this.sendMail({ to, subject: baseContent.subject, html });
return this.sendMail({ to, subject, html, attachments });
}

async sendReviewRequestEmail(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class WorkspaceService {

await this.mailer.sendMemberAcceptedEmail(inviter.email, {
inviteeName: invitee.name,
workspaceName: workspace.name,
workspace,
});
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JSX } from 'react';
import type { CSSProperties, JSX } from 'react';

export type EmailTemplateProps = {
title: string;
Expand All @@ -18,7 +18,7 @@ export const DefaultProps: EmailTemplateProps = {
'If you did not request this invitation, please ignore this email.',
};

export const BasicTextStyle: React.CSSProperties = {
export const BasicTextStyle: CSSProperties = {
fontSize: '12px',
fontWeight: '400',
lineHeight: '20px',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Container, Img, Link, Row, Section } from '@react-email/components';
import type { CSSProperties } from 'react';

import { BasicTextStyle } from './common';

const TextStyles: React.CSSProperties = {
const TextStyles: CSSProperties = {
...BasicTextStyle,
color: '#8e8d91',
margin: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export type { EmailTemplateProps } from './common';
export { EmailTemplate } from './template';
export { WorkspaceAvatar } from './workspace-avatar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const WorkspaceAvatar = (props: { workspaceName: string }) => {
return (
<>
<img
src="cid:workspaceAvatar"
alt=""
width="24px"
height="24px"
style={{
width: '24px',
height: '24px',
marginLeft: '4px',
borderRadius: '12px',
objectFit: 'cover',
verticalAlign: 'middle',
}}
/>
<span style={{ fontWeight: 500, marginRight: '4px' }}>
{props.workspaceName || 'Unknown Workspace'}
</span>
</>
);
};
17 changes: 17 additions & 0 deletions packages/backend/server/src/mail-templates/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import ChangePassword, { type ChangePasswordProps } from './password-change';
import SetPassword, { type SetPasswordProps } from './password-set';
import SignIn, { type SignInProps } from './sign-in';
import SignUp, { type SignUpProps } from './sign-up';
import TeamWorkspaceUpgraded, {
type TeamWorkspaceUpgradedProps,
} from './team-workspace-upgraded';

type EmailContent = Pick<SMTPTransport.Options, 'subject' | 'html'>;

Expand Down Expand Up @@ -125,3 +128,17 @@ export const renderMemberLeaveMail = async (
html: await render(<MemberLeave {...props} />),
};
};

// ================ Team ================

export const renderTeamWorkspaceUpgradedMail = async (
props: TeamWorkspaceUpgradedProps
): Promise<EmailContent> => {
const { workspaceName, isOwner } = props;
return {
subject: isOwner
? 'Your workspace has been upgraded to team workspace! 🎉'
: `${workspaceName} has been upgraded to team workspace! 🎉`,
html: await render(<TeamWorkspaceUpgraded {...props} />),
};
};
11 changes: 9 additions & 2 deletions packages/backend/server/src/mail-templates/member-accepted.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { EmailTemplate } from './components';
import { Text } from '@react-email/components';

import { EmailTemplate, WorkspaceAvatar } from './components';

export type MemberAcceptedProps = {
inviteeName: string;
Expand All @@ -10,7 +12,12 @@ export default function MemberAccepted(props: MemberAcceptedProps) {
return (
<EmailTemplate
title={`${inviteeName} accepted your invitation`}
content={`${inviteeName} has joined ${workspaceName}`}
content={
<Text>
{inviteeName} has joined
<WorkspaceAvatar workspaceName={workspaceName} />
</Text>
}
/>
);
}
11 changes: 9 additions & 2 deletions packages/backend/server/src/mail-templates/member-leave.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { EmailTemplate } from './components';
import { Text } from '@react-email/components';

import { EmailTemplate, WorkspaceAvatar } from './components';

export type MemberLeaveProps = {
inviteeName: string;
Expand All @@ -10,7 +12,12 @@ export default function MemberLeave(props: MemberLeaveProps) {
return (
<EmailTemplate
title={`${inviteeName} left ${workspaceName}`}
content={`${inviteeName} has left your workspace`}
content={
<Text>
{inviteeName} has left your workspace
<WorkspaceAvatar workspaceName={workspaceName} />
</Text>
}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Text } from '@react-email/components';

import { EmailTemplate, WorkspaceAvatar } from './components';

export type TeamWorkspaceUpgradedProps = {
workspaceName: string;
isOwner: boolean;
url?: string;
};

const MailContent = (props: TeamWorkspaceUpgradedProps) => {
const { isOwner, workspaceName } = props;
return (
<Text>
{isOwner ? (
<>
<WorkspaceAvatar workspaceName={workspaceName} />
has been upgraded to team workspace with the following benefits:
</>
) : (
<>
Great news! <WorkspaceAvatar workspaceName={workspaceName} /> has been
upgraded to team workspace by the workspace owner.
<br />
You now have access to the following enhanced features:
</>
)}
<br /> ✓ 100 GB initial storage + 20 GB per seat
<br /> ✓ 500 MB of maximum file size
<br /> ✓ Unlimited team members (10+ seats)
<br /> ✓ Multiple admin roles
<br /> ✓ Priority customer support
</Text>
);
};

export default function TeamWorkspaceUpgraded(
props: TeamWorkspaceUpgradedProps
) {
return (
<EmailTemplate
title="Welcome to the team workspace!"
content={<MailContent {...props} />}
buttonContent="Open Workspace"
buttonUrl={props.url || 'https://app.affine.pro'}
/>
);
}

0 comments on commit cb96048

Please sign in to comment.