feat: update team email templates. (#1229)
Updates the email templates to include team information when sent from a team context.
This commit is contained in:
@@ -196,6 +196,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
|||||||
}
|
}
|
||||||
avatarFallback={formatAvatarFallback(team.name)}
|
avatarFallback={formatAvatarFallback(team.name)}
|
||||||
primaryText={team.name}
|
primaryText={team.name}
|
||||||
|
textSectionClassName="w-[200px]"
|
||||||
secondaryText={
|
secondaryText={
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<motion.span
|
<motion.span
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export interface TemplateDocumentInviteProps {
|
|||||||
assetBaseUrl: string;
|
assetBaseUrl: string;
|
||||||
role: RecipientRole;
|
role: RecipientRole;
|
||||||
selfSigner: boolean;
|
selfSigner: boolean;
|
||||||
|
isTeamInvite: boolean;
|
||||||
|
teamName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TemplateDocumentInvite = ({
|
export const TemplateDocumentInvite = ({
|
||||||
@@ -21,6 +23,8 @@ export const TemplateDocumentInvite = ({
|
|||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
role,
|
role,
|
||||||
selfSigner,
|
selfSigner,
|
||||||
|
isTeamInvite,
|
||||||
|
teamName,
|
||||||
}: TemplateDocumentInviteProps) => {
|
}: TemplateDocumentInviteProps) => {
|
||||||
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION[role];
|
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION[role];
|
||||||
|
|
||||||
@@ -36,6 +40,12 @@ export const TemplateDocumentInvite = ({
|
|||||||
<br />
|
<br />
|
||||||
{`"${documentName}"`}
|
{`"${documentName}"`}
|
||||||
</>
|
</>
|
||||||
|
) : isTeamInvite ? (
|
||||||
|
<>
|
||||||
|
{`${inviterName} on behalf of ${teamName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||||
|
<br />
|
||||||
|
{`"${documentName}"`}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{`${inviterName} has invited you to ${actionVerb.toLowerCase()}`}
|
{`${inviterName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ export const ConfirmTeamEmailTemplate = ({
|
|||||||
<li className="mt-1 text-sm">
|
<li className="mt-1 text-sm">
|
||||||
Allow document recipients to reply directly to this email address
|
Allow document recipients to reply directly to this email address
|
||||||
</li>
|
</li>
|
||||||
|
<li className="mt-1 text-sm">
|
||||||
|
Send documents on behalf of the team using the email address
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<Text className="mt-2 text-sm">
|
<Text className="mt-2 text-sm">
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInvitePro
|
|||||||
customBody?: string;
|
customBody?: string;
|
||||||
role: RecipientRole;
|
role: RecipientRole;
|
||||||
selfSigner?: boolean;
|
selfSigner?: boolean;
|
||||||
|
isTeamInvite?: boolean;
|
||||||
|
teamName?: string;
|
||||||
|
teamEmail?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentInviteEmailTemplate = ({
|
export const DocumentInviteEmailTemplate = ({
|
||||||
@@ -34,11 +37,15 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
customBody,
|
customBody,
|
||||||
role,
|
role,
|
||||||
selfSigner = false,
|
selfSigner = false,
|
||||||
|
isTeamInvite = false,
|
||||||
|
teamName,
|
||||||
}: DocumentInviteEmailTemplateProps) => {
|
}: DocumentInviteEmailTemplateProps) => {
|
||||||
const action = RECIPIENT_ROLES_DESCRIPTION[role].actionVerb.toLowerCase();
|
const action = RECIPIENT_ROLES_DESCRIPTION[role].actionVerb.toLowerCase();
|
||||||
|
|
||||||
const previewText = selfSigner
|
const previewText = selfSigner
|
||||||
? `Please ${action} your document ${documentName}`
|
? `Please ${action} your document ${documentName}`
|
||||||
|
: isTeamInvite
|
||||||
|
? `${inviterName} on behalf of ${teamName} has invited you to ${action} ${documentName}`
|
||||||
: `${inviterName} has invited you to ${action} ${documentName}`;
|
: `${inviterName} has invited you to ${action} ${documentName}`;
|
||||||
|
|
||||||
const getAssetUrl = (path: string) => {
|
const getAssetUrl = (path: string) => {
|
||||||
@@ -76,6 +83,8 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
assetBaseUrl={assetBaseUrl}
|
assetBaseUrl={assetBaseUrl}
|
||||||
role={role}
|
role={role}
|
||||||
selfSigner={selfSigner}
|
selfSigner={selfSigner}
|
||||||
|
isTeamInvite={isTeamInvite}
|
||||||
|
teamName={teamName}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamEmail: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
prisma.recipient.findFirstOrThrow({
|
prisma.recipient.findFirstOrThrow({
|
||||||
@@ -67,7 +73,7 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { documentMeta } = document;
|
const { documentMeta, team } = document;
|
||||||
|
|
||||||
if (recipient.role === RecipientRole.CC) {
|
if (recipient.role === RecipientRole.CC) {
|
||||||
return;
|
return;
|
||||||
@@ -75,6 +81,7 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
|
|
||||||
const customEmail = document?.documentMeta;
|
const customEmail = document?.documentMeta;
|
||||||
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
||||||
|
const isTeamDocument = document.teamId !== null;
|
||||||
|
|
||||||
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
||||||
|
|
||||||
@@ -96,6 +103,11 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
emailSubject = `Please ${recipientActionVerb} this document created by your direct template`;
|
emailSubject = `Please ${recipientActionVerb} this document created by your direct template`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTeamDocument && team) {
|
||||||
|
emailSubject = `${team.name} invited you to ${recipientActionVerb} a document`;
|
||||||
|
emailMessage = `${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||||
|
}
|
||||||
|
|
||||||
const customEmailTemplate = {
|
const customEmailTemplate = {
|
||||||
'signer.name': name,
|
'signer.name': name,
|
||||||
'signer.email': email,
|
'signer.email': email,
|
||||||
@@ -108,12 +120,15 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
|||||||
const template = createElement(DocumentInviteEmailTemplate, {
|
const template = createElement(DocumentInviteEmailTemplate, {
|
||||||
documentName: document.title,
|
documentName: document.title,
|
||||||
inviterName: user.name || undefined,
|
inviterName: user.name || undefined,
|
||||||
inviterEmail: user.email,
|
inviterEmail: isTeamDocument ? team?.teamEmail?.email || user.email : user.email,
|
||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
signDocumentLink,
|
signDocumentLink,
|
||||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||||
role: recipient.role,
|
role: recipient.role,
|
||||||
selfSigner,
|
selfSigner,
|
||||||
|
isTeamInvite: isTeamDocument,
|
||||||
|
teamName: team?.name,
|
||||||
|
teamEmail: team?.teamEmail?.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
await io.runTask('send-signing-email', async () => {
|
await io.runTask('send-signing-email', async () => {
|
||||||
|
|||||||
@@ -58,10 +58,17 @@ export const resendDocument = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
documentMeta: true,
|
documentMeta: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
teamEmail: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const customEmail = document?.documentMeta;
|
const customEmail = document?.documentMeta;
|
||||||
|
const isTeamDocument = document?.team !== null;
|
||||||
|
|
||||||
if (!document) {
|
if (!document) {
|
||||||
throw new Error('Document not found');
|
throw new Error('Document not found');
|
||||||
@@ -90,9 +97,21 @@ export const resendDocument = async ({
|
|||||||
const { email, name } = recipient;
|
const { email, name } = recipient;
|
||||||
const selfSigner = email === user.email;
|
const selfSigner = email === user.email;
|
||||||
|
|
||||||
const selfSignerCustomEmail = `You have initiated the document ${`"${document.title}"`} that requires you to ${RECIPIENT_ROLES_DESCRIPTION[
|
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||||
recipient.role
|
const recipientActionVerb = actionVerb.toLowerCase();
|
||||||
].actionVerb.toLowerCase()} it.`;
|
|
||||||
|
let emailMessage = customEmail?.message || '';
|
||||||
|
let emailSubject = `Reminder: Please ${recipientActionVerb} this document`;
|
||||||
|
|
||||||
|
if (selfSigner) {
|
||||||
|
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||||
|
emailSubject = `Reminder: Please ${recipientActionVerb} your document`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTeamDocument && document.team) {
|
||||||
|
emailSubject = `Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||||
|
emailMessage = `${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||||
|
}
|
||||||
|
|
||||||
const customEmailTemplate = {
|
const customEmailTemplate = {
|
||||||
'signer.name': name,
|
'signer.name': name,
|
||||||
@@ -106,23 +125,16 @@ export const resendDocument = async ({
|
|||||||
const template = createElement(DocumentInviteEmailTemplate, {
|
const template = createElement(DocumentInviteEmailTemplate, {
|
||||||
documentName: document.title,
|
documentName: document.title,
|
||||||
inviterName: user.name || undefined,
|
inviterName: user.name || undefined,
|
||||||
inviterEmail: user.email,
|
inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email,
|
||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
signDocumentLink,
|
signDocumentLink,
|
||||||
customBody: renderCustomEmailTemplate(
|
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||||
selfSigner && !customEmail?.message ? selfSignerCustomEmail : customEmail?.message || '',
|
|
||||||
customEmailTemplate,
|
|
||||||
),
|
|
||||||
role: recipient.role,
|
role: recipient.role,
|
||||||
selfSigner,
|
selfSigner,
|
||||||
|
isTeamInvite: isTeamDocument,
|
||||||
|
teamName: document.team?.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
|
||||||
|
|
||||||
const emailSubject = selfSigner
|
|
||||||
? `Reminder: Please ${actionVerb.toLowerCase()} your document`
|
|
||||||
: `Reminder: Please ${actionVerb.toLowerCase()} this document`;
|
|
||||||
|
|
||||||
await prisma.$transaction(
|
await prisma.$transaction(
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
await mailer.sendMail({
|
await mailer.sendMail({
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|||||||
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||||
|
|
||||||
import { prisma } from '..';
|
import { prisma } from '..';
|
||||||
import { DocumentDataType, DocumentSource, Role } from '../client';
|
import { DocumentDataType, DocumentSource, Role, TeamMemberRole } from '../client';
|
||||||
|
|
||||||
export const seedDatabase = async () => {
|
export const seedDatabase = async () => {
|
||||||
const examplePdf = fs
|
const examplePdf = fs
|
||||||
@@ -67,4 +67,64 @@ export const seedDatabase = async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const testUsers = [
|
||||||
|
'test@documenso.com',
|
||||||
|
'test2@documenso.com',
|
||||||
|
'test3@documenso.com',
|
||||||
|
'test4@documenso.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
const createdUsers = [];
|
||||||
|
|
||||||
|
for (const email of testUsers) {
|
||||||
|
const testUser = await prisma.user.upsert({
|
||||||
|
where: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
name: 'Test User',
|
||||||
|
email: email,
|
||||||
|
emailVerified: new Date(),
|
||||||
|
password: hashSync('password'),
|
||||||
|
roles: [Role.USER],
|
||||||
|
},
|
||||||
|
update: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
createdUsers.push(testUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
const team1 = await prisma.team.create({
|
||||||
|
data: {
|
||||||
|
name: 'Team 1',
|
||||||
|
url: 'team1',
|
||||||
|
ownerUserId: createdUsers[0].id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const team2 = await prisma.team.create({
|
||||||
|
data: {
|
||||||
|
name: 'Team 2',
|
||||||
|
url: 'team2',
|
||||||
|
ownerUserId: createdUsers[1].id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const team of [team1, team2]) {
|
||||||
|
await prisma.teamMember.createMany({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
teamId: team.id,
|
||||||
|
userId: createdUsers[1].id,
|
||||||
|
role: TeamMemberRole.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
teamId: team.id,
|
||||||
|
userId: createdUsers[2].id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user