diff --git a/packages/lib/jobs/client.ts b/packages/lib/jobs/client.ts index 636f66b48..f604eb453 100644 --- a/packages/lib/jobs/client.ts +++ b/packages/lib/jobs/client.ts @@ -2,6 +2,7 @@ import { JobClient } from './client/client'; import { SEND_ADMIN_USER_CREATED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-admin-user-created-email'; import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/send-confirmation-email'; import { SEND_DOCUMENT_CANCELLED_EMAILS_JOB_DEFINITION } from './definitions/emails/send-document-cancelled-emails'; +import { SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION } from './definitions/emails/send-document-completed-emails'; import { SEND_DOCUMENT_CREATED_FROM_DIRECT_TEMPLATE_EMAIL_JOB_DEFINITION } from './definitions/emails/send-document-created-from-direct-template-email'; import { SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-joined-email'; import { SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-left-email'; @@ -41,6 +42,7 @@ export const jobsClient = new JobClient([ SEND_PASSWORD_RESET_SUCCESS_EMAIL_JOB_DEFINITION, SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION, SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION, + SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION, SEND_DOCUMENT_CANCELLED_EMAILS_JOB_DEFINITION, SEND_DOCUMENT_CREATED_FROM_DIRECT_TEMPLATE_EMAIL_JOB_DEFINITION, SEND_OWNER_RECIPIENT_EXPIRED_EMAIL_JOB_DEFINITION, diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/jobs/definitions/emails/send-document-completed-emails.handler.ts similarity index 83% rename from packages/lib/server-only/document/send-completed-email.ts rename to packages/lib/jobs/definitions/emails/send-document-completed-emails.handler.ts index 40bdf6740..d48f7b639 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/jobs/definitions/emails/send-document-completed-emails.handler.ts @@ -5,29 +5,26 @@ import { msg } from '@lingui/core/macro'; import { DocumentSource, EnvelopeType } from '@prisma/client'; import { createElement } from 'react'; -import { getI18nInstance } from '../../client-only/providers/i18n-server'; -import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app'; -import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; -import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; -import type { RequestMetadata } from '../../universal/extract-request-metadata'; -import { getFileServerSide } from '../../universal/upload/get-file.server'; -import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; -import type { EnvelopeIdOptions } from '../../utils/envelope'; -import { unsafeBuildEnvelopeIdQuery } from '../../utils/envelope'; -import { isRecipientEmailValidForSending } from '../../utils/recipients'; -import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template'; -import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; -import { formatDocumentsPath } from '../../utils/teams'; -import { getEmailContext } from '../email/get-email-context'; +import { getI18nInstance } from '../../../client-only/providers/i18n-server'; +import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; +import { getEmailContext } from '../../../server-only/email/get-email-context'; +import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs'; +import { extractDerivedDocumentEmailSettings } from '../../../types/document-email'; +import { getFileServerSide } from '../../../universal/upload/get-file.server'; +import { createDocumentAuditLogData } from '../../../utils/document-audit-logs'; +import { unsafeBuildEnvelopeIdQuery } from '../../../utils/envelope'; +import { isRecipientEmailValidForSending } from '../../../utils/recipients'; +import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template'; +import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n'; +import { formatDocumentsPath } from '../../../utils/teams'; +import type { JobRunIO } from '../../client/_internal/job'; +import type { TSendDocumentCompletedEmailsJobDefinition } from './send-document-completed-emails'; -export interface SendDocumentOptions { - id: EnvelopeIdOptions; - requestMetadata?: RequestMetadata; -} +export const run = async ({ payload, io }: { payload: TSendDocumentCompletedEmailsJobDefinition; io: JobRunIO }) => { + const { envelopeId, requestMetadata } = payload; -export const sendCompletedEmail = async ({ id, requestMetadata }: SendDocumentOptions) => { const envelope = await prisma.envelope.findUnique({ - where: unsafeBuildEnvelopeIdQuery(id, EnvelopeType.DOCUMENT), + where: unsafeBuildEnvelopeIdQuery({ type: 'envelopeId', id: envelopeId }, EnvelopeType.DOCUMENT), include: { envelopeItems: { include: { diff --git a/packages/lib/jobs/definitions/emails/send-document-completed-emails.ts b/packages/lib/jobs/definitions/emails/send-document-completed-emails.ts new file mode 100644 index 000000000..c6366fa17 --- /dev/null +++ b/packages/lib/jobs/definitions/emails/send-document-completed-emails.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata'; +import type { JobDefinition } from '../../client/_internal/job'; + +const SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_ID = 'send.document.completed.emails'; + +const SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_SCHEMA = z.object({ + envelopeId: z.string(), + requestMetadata: ZRequestMetadataSchema.optional(), +}); + +export type TSendDocumentCompletedEmailsJobDefinition = z.infer< + typeof SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_SCHEMA +>; + +export const SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION = { + id: SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_ID, + name: 'Send Document Completed Emails', + version: '1.0.0', + trigger: { + name: SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_ID, + schema: SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_SCHEMA, + }, + handler: async ({ payload, io }) => { + const handler = await import('./send-document-completed-emails.handler'); + + await handler.run({ payload, io }); + }, +} as const satisfies JobDefinition< + typeof SEND_DOCUMENT_COMPLETED_EMAILS_JOB_DEFINITION_ID, + TSendDocumentCompletedEmailsJobDefinition +>; diff --git a/packages/lib/jobs/definitions/internal/seal-document.handler.ts b/packages/lib/jobs/definitions/internal/seal-document.handler.ts index 7748d7451..d78212642 100644 --- a/packages/lib/jobs/definitions/internal/seal-document.handler.ts +++ b/packages/lib/jobs/definitions/internal/seal-document.handler.ts @@ -14,7 +14,6 @@ import { groupBy } from 'remeda'; import { NEXT_PRIVATE_USE_PLAYWRIGHT_PDF } from '../../../constants/app'; import { AppError, AppErrorCode } from '../../../errors/app-error'; -import { sendCompletedEmail } from '../../../server-only/document/send-completed-email'; import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf'; import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf'; import { insertFieldInPDFV1 } from '../../../server-only/pdf/insert-field-in-pdf-v1'; @@ -31,6 +30,7 @@ import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fiel import { isDocumentCompleted } from '../../../utils/document'; import { createDocumentAuditLogData } from '../../../utils/document-audit-logs'; import { mapDocumentIdToSecondaryId } from '../../../utils/envelope'; +import { jobs } from '../../client'; import type { JobRunIO } from '../../client/_internal/job'; import type { TSealDocumentJobDefinition } from './seal-document'; @@ -294,21 +294,6 @@ export const run = async ({ payload, io }: { payload: TSealDocumentJobDefinition }; }); - await io.runTask('send-completed-email', async () => { - let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected; - - if (isResealing && !isDocumentCompleted(envelopeStatus)) { - shouldSendCompletedEmail = sendEmail; - } - - if (shouldSendCompletedEmail) { - await sendCompletedEmail({ - id: { type: 'envelopeId', id: envelopeId }, - requestMetadata, - }); - } - }); - const updatedEnvelope = await prisma.envelope.findFirstOrThrow({ where: { id: envelopeId, @@ -325,6 +310,22 @@ export const run = async ({ payload, io }: { payload: TSealDocumentJobDefinition userId: updatedEnvelope.userId, teamId: updatedEnvelope.teamId ?? undefined, }); + + let shouldSendCompletedEmail = sendEmail && !isResealing && !isRejected; + + if (isResealing && !isDocumentCompleted(envelopeStatus)) { + shouldSendCompletedEmail = sendEmail; + } + + if (shouldSendCompletedEmail) { + await jobs.triggerJob({ + name: 'send.document.completed.emails', + payload: { + envelopeId, + requestMetadata, + }, + }); + } }; type DecorateAndSignPdfOptions = {