From 52afae331e6dfaf907894aff446d8f732bb04a2c Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Wed, 13 Mar 2024 09:50:37 +0530 Subject: [PATCH 1/3] chore: updated to send email to doc owners Signed-off-by: Adithya Krishna --- .../server-only/document/send-completed-email.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index 812e54ba3..2ef1da851 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -10,6 +10,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { getFile } from '../../universal/upload/get-file'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; +import { getUserById } from '../user/get-user-by-id'; export interface SendDocumentOptions { documentId: number; @@ -40,6 +41,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo await Promise.all( document.Recipient.map(async (recipient) => { const { email, name, token } = recipient; + const user = await getUserById({ id: document.userId }); const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; @@ -52,10 +54,16 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo await prisma.$transaction( async (tx) => { await mailer.sendMail({ - to: { - address: email, - name, - }, + to: [ + { + address: email, + name, + }, + { + address: user.email, + name: user.name!, + }, + ], from: { name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', From f012826b6bfd0929b47e4a660d813e8f78218c00 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Fri, 15 Mar 2024 22:26:31 +0530 Subject: [PATCH 2/3] chore: send document owner email Signed-off-by: Adithya Krishna --- .../lib/server-only/document/seal-document.ts | 5 ++++- .../server-only/document/send-completed-email.ts | 16 +++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index 95b7d9dc4..d9cf8d392 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -12,6 +12,7 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/ import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { signPdf } from '@documenso/signing'; +import { getRequiredServerComponentSession } from '../../next-auth/get-server-component-session'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { getFile } from '../../universal/upload/get-file'; import { putFile } from '../../universal/upload/put-file'; @@ -46,6 +47,8 @@ export const sealDocument = async ({ const { documentData } = document; + const { user: documentOwner } = await getRequiredServerComponentSession(); + if (!documentData) { throw new Error(`Document ${document.id} has no document data`); } @@ -169,7 +172,7 @@ export const sealDocument = async ({ }); if (sendEmail && !isResealing) { - await sendCompletedEmail({ documentId, requestMetadata }); + await sendCompletedEmail({ documentId, requestMetadata, documentOwner }); } await triggerWebhook({ diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index 2ef1da851..b797231c8 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -4,20 +4,25 @@ import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed'; import { prisma } from '@documenso/prisma'; +import type { User } from '@documenso/prisma/client'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { getFile } from '../../universal/upload/get-file'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; -import { getUserById } from '../user/get-user-by-id'; export interface SendDocumentOptions { documentId: number; requestMetadata?: RequestMetadata; + documentOwner: User; } -export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDocumentOptions) => { +export const sendCompletedEmail = async ({ + documentId, + requestMetadata, + documentOwner, +}: SendDocumentOptions) => { const document = await prisma.document.findUnique({ where: { id: documentId, @@ -41,7 +46,6 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo await Promise.all( document.Recipient.map(async (recipient) => { const { email, name, token } = recipient; - const user = await getUserById({ id: document.userId }); const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; @@ -55,13 +59,15 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo async (tx) => { await mailer.sendMail({ to: [ + // To send email to recipient of the document { address: email, name, }, + // To send email to owner of the document { - address: user.email, - name: user.name!, + address: documentOwner.email, + name: documentOwner.name!, }, ], from: { From a7594c9b3c8fe4e934c3c2efbe1684e96d8cc59f Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Sat, 16 Mar 2024 11:06:33 +0000 Subject: [PATCH 3/3] fix: update sending logic --- .../lib/server-only/document/seal-document.ts | 5 +- .../document/send-completed-email.ts | 168 +++++++++++------- 2 files changed, 109 insertions(+), 64 deletions(-) diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index d9cf8d392..95b7d9dc4 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -12,7 +12,6 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/ import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { signPdf } from '@documenso/signing'; -import { getRequiredServerComponentSession } from '../../next-auth/get-server-component-session'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { getFile } from '../../universal/upload/get-file'; import { putFile } from '../../universal/upload/put-file'; @@ -47,8 +46,6 @@ export const sealDocument = async ({ const { documentData } = document; - const { user: documentOwner } = await getRequiredServerComponentSession(); - if (!documentData) { throw new Error(`Document ${document.id} has no document data`); } @@ -172,7 +169,7 @@ export const sealDocument = async ({ }); if (sendEmail && !isResealing) { - await sendCompletedEmail({ documentId, requestMetadata, documentOwner }); + await sendCompletedEmail({ documentId, requestMetadata }); } await triggerWebhook({ diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index b797231c8..7ff99bbdf 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -4,7 +4,6 @@ import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed'; import { prisma } from '@documenso/prisma'; -import type { User } from '@documenso/prisma/client'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; @@ -15,14 +14,9 @@ import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; export interface SendDocumentOptions { documentId: number; requestMetadata?: RequestMetadata; - documentOwner: User; } -export const sendCompletedEmail = async ({ - documentId, - requestMetadata, - documentOwner, -}: SendDocumentOptions) => { +export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDocumentOptions) => { const document = await prisma.document.findUnique({ where: { id: documentId, @@ -30,6 +24,13 @@ export const sendCompletedEmail = async ({ include: { documentData: true, Recipient: true, + User: true, + team: { + select: { + id: true, + url: true, + }, + }, }, }); @@ -41,69 +42,116 @@ export const sendCompletedEmail = async ({ throw new Error('Document has no recipients'); } - const buffer = await getFile(document.documentData); + const { User: owner } = document; + + const completedDocument = await getFile(document.documentData); + + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; + + let documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/documents/${document.id}`; + + if (document.team?.url) { + documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${document.team.url}/documents/${ + document.id + }`; + } + + // If the document owner is not a recipient then send the email to them separately + if (!document.Recipient.find((recipient) => recipient.email === owner.email)) { + const template = createElement(DocumentCompletedEmailTemplate, { + documentName: document.title, + assetBaseUrl, + downloadLink: documentOwnerDownloadLink, + }); + + await mailer.sendMail({ + to: [ + { + name: owner.name || '', + address: owner.email, + }, + ], + from: { + name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', + address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', + }, + subject: 'Signing Complete!', + html: render(template), + text: render(template, { plainText: true }), + attachments: [ + { + filename: document.title, + content: Buffer.from(completedDocument), + }, + ], + }); + + await prisma.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT, + documentId: document.id, + user: null, + requestMetadata, + data: { + emailType: 'DOCUMENT_COMPLETED', + recipientEmail: owner.email, + recipientName: owner.name, + recipientId: owner.id, + recipientRole: 'OWNER', + isResending: false, + }, + }), + }); + } await Promise.all( document.Recipient.map(async (recipient) => { - const { email, name, token } = recipient; - - const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; + const downloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}/complete`; const template = createElement(DocumentCompletedEmailTemplate, { documentName: document.title, assetBaseUrl, - downloadLink: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}/complete`, + downloadLink: recipient.email === owner.email ? documentOwnerDownloadLink : downloadLink, }); - await prisma.$transaction( - async (tx) => { - await mailer.sendMail({ - to: [ - // To send email to recipient of the document - { - address: email, - name, - }, - // To send email to owner of the document - { - address: documentOwner.email, - name: documentOwner.name!, - }, - ], - from: { - name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', - address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', - }, - subject: 'Signing Complete!', - html: render(template), - text: render(template, { plainText: true }), - attachments: [ - { - filename: document.title, - content: Buffer.from(buffer), - }, - ], - }); - - await tx.documentAuditLog.create({ - data: createDocumentAuditLogData({ - type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT, - documentId: document.id, - user: null, - requestMetadata, - data: { - emailType: 'DOCUMENT_COMPLETED', - recipientEmail: recipient.email, - recipientName: recipient.name, - recipientId: recipient.id, - recipientRole: recipient.role, - isResending: false, - }, - }), - }); + await mailer.sendMail({ + to: [ + { + name: recipient.name, + address: recipient.email, + }, + ], + from: { + name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso', + address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com', }, - { timeout: 30_000 }, - ); + subject: 'Signing Complete!', + html: render(template), + text: render(template, { plainText: true }), + attachments: [ + { + filename: document.title, + content: Buffer.from(completedDocument), + }, + ], + }); + + await prisma.documentAuditLog.create({ + data: createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT, + documentId: document.id, + user: null, + requestMetadata, + data: { + emailType: 'DOCUMENT_COMPLETED', + recipientEmail: recipient.email, + recipientName: recipient.name, + recipientId: recipient.id, + recipientRole: recipient.role, + isResending: false, + }, + }), + }); }), ); };