import { createElement } from 'react'; import { msg } from '@lingui/core/macro'; import { DocumentStatus, OrganisationType, RecipientRole, SigningStatus } from '@prisma/client'; import { mailer } from '@documenso/email/mailer'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; import { RECIPIENT_ROLES_DESCRIPTION, RECIPIENT_ROLE_TO_EMAIL_TYPE, } from '@documenso/lib/constants/recipient-roles'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs'; import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template'; import { prisma } from '@documenso/prisma'; import { getI18nInstance } from '../../client-only/providers/i18n-server'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; import { isDocumentCompleted } from '../../utils/document'; import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; import { getEmailContext } from '../email/get-email-context'; import { getDocumentWhereInput } from './get-document-by-id'; export type ResendDocumentOptions = { documentId: number; userId: number; recipients: number[]; teamId: number; requestMetadata: ApiRequestMetadata; }; export const resendDocument = async ({ documentId, userId, recipients, teamId, requestMetadata, }: ResendDocumentOptions): Promise => { const user = await prisma.user.findFirstOrThrow({ where: { id: userId, }, }); const { documentWhereInput } = await getDocumentWhereInput({ documentId, userId, teamId, }); const document = await prisma.document.findUnique({ where: documentWhereInput, include: { recipients: true, documentMeta: true, team: { select: { teamEmail: true, name: true, }, }, }, }); const customEmail = document?.documentMeta; if (!document) { throw new Error('Document not found'); } if (document.recipients.length === 0) { throw new Error('Document has no recipients'); } if (document.status === DocumentStatus.DRAFT) { throw new Error('Can not send draft document'); } if (isDocumentCompleted(document.status)) { throw new Error('Can not send completed document'); } const recipientsToRemind = document.recipients.filter( (recipient) => recipients.includes(recipient.id) && recipient.signingStatus === SigningStatus.NOT_SIGNED, ); const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings( document.documentMeta, ).recipientSigningRequest; if (!isRecipientSigningRequestEmailEnabled) { return; } const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } = await getEmailContext({ emailType: 'RECIPIENT', source: { type: 'team', teamId: document.teamId, }, meta: document.documentMeta || null, }); await Promise.all( recipientsToRemind.map(async (recipient) => { if (recipient.role === RecipientRole.CC) { return; } const i18n = await getI18nInstance(emailLanguage); const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role]; const { email, name } = recipient; const selfSigner = email === user.email; const recipientActionVerb = i18n ._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb) .toLowerCase(); let emailMessage = customEmail?.message || ''; let emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} this document`); if (selfSigner) { emailMessage = i18n._( msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`, ); emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} your document`); } if (organisationType === OrganisationType.ORGANISATION) { emailSubject = i18n._( msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`, ); emailMessage = customEmail?.message || i18n._( msg`${user.name || user.email} on behalf of "${document.team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`, ); } const customEmailTemplate = { 'signer.name': name, 'signer.email': email, 'document.name': document.title, }; const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`; const template = createElement(DocumentInviteEmailTemplate, { documentName: document.title, inviterName: user.name || undefined, inviterEmail: organisationType === OrganisationType.ORGANISATION ? document.team?.teamEmail?.email || user.email : user.email, assetBaseUrl, signDocumentLink, customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate), role: recipient.role, selfSigner, organisationType, teamName: document.team?.name, }); const [html, text] = await Promise.all([ renderEmailWithI18N(template, { lang: emailLanguage, branding, }), renderEmailWithI18N(template, { lang: emailLanguage, branding, plainText: true, }), ]); await prisma.$transaction( async (tx) => { await mailer.sendMail({ to: { address: email, name, }, from: senderEmail, replyTo: replyToEmail, subject: customEmail?.subject ? renderCustomEmailTemplate( i18n._(msg`Reminder: ${customEmail.subject}`), customEmailTemplate, ) : emailSubject, html, text, }); await tx.documentAuditLog.create({ data: createDocumentAuditLogData({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT, documentId: document.id, metadata: requestMetadata, data: { emailType: recipientEmailType, recipientEmail: recipient.email, recipientName: recipient.name, recipientRole: recipient.role, recipientId: recipient.id, isResending: true, }, }), }); }, { timeout: 30_000 }, ); }), ); };