feat(signing): internal "who signed" + completion email alerts to configurable recipients
Adds a per-port `signing_notification_recipients` setting (users / roles / explicit emails via the existing RecipientPicker) and fires a branded internal email on (a) each party signing and (b) full completion — replicating the legacy "Document Signed" / "EOI Complete Update Status" Activepieces flows that staff relied on. - New branded template `signing-status-notification.tsx` (per-signer progress + completion variants, deep-links into the CRM). - New `sendSigningStatusNotification` resolver in document-signing-emails: resolves recipients, falls back to the port reply-to (sales@) when the list is empty so alerts are never silently dropped, per-recipient send. - Wired into `handleRecipientSigned` (first signed transition) and `handleDocumentCompleted` (idempotent, fires once) — reached by both the Documenso webhook and the 5-min poll. Fully guarded so a notification failure never undoes a signing side effect. Respects EMAIL_REDIRECT_TO. - Admin UI: `Document signing alerts` recipients card in settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012iJPYbh5X53iBh9h7ffQoy
This commit is contained in:
50
tests/unit/email/signing-status-notification.test.ts
Normal file
50
tests/unit/email/signing-status-notification.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { signingStatusNotificationEmail } from '@/lib/email/templates/signing-status-notification';
|
||||
|
||||
describe('signingStatusNotificationEmail', () => {
|
||||
it('renders a per-signer "has signed" alert with progress + CRM link', async () => {
|
||||
const { subject, html, text } = await signingStatusNotificationEmail({
|
||||
event: 'signed',
|
||||
documentLabel: 'Expression of Interest',
|
||||
clientName: 'Jane Doe',
|
||||
portName: 'Port Nimara',
|
||||
crmUrl: 'https://crm.portnimara.com/port-nimara/documents/abc',
|
||||
signerName: 'Jane Doe',
|
||||
signerRole: 'client',
|
||||
signedCount: 1,
|
||||
totalCount: 3,
|
||||
});
|
||||
|
||||
// Subject names who signed + the deal so sales can triage at a glance.
|
||||
expect(subject).toContain('Jane Doe');
|
||||
expect(subject).toContain('signed');
|
||||
// Body states the signing event, the document, and progress.
|
||||
expect(html).toContain('Jane Doe');
|
||||
expect(html).toContain('has signed');
|
||||
expect(html).toContain('Expression of Interest');
|
||||
expect(html).toContain('1 of 3');
|
||||
// Internal recipients get a deep link into the CRM, not a signing link.
|
||||
expect(html).toContain('https://crm.portnimara.com/port-nimara/documents/abc');
|
||||
expect(text).toContain('Jane Doe');
|
||||
expect(text).toContain('1 of 3');
|
||||
});
|
||||
|
||||
it('renders a completion alert when all parties have signed', async () => {
|
||||
const { subject, html, text } = await signingStatusNotificationEmail({
|
||||
event: 'completed',
|
||||
documentLabel: 'Sales Contract',
|
||||
clientName: 'Acme Holdings',
|
||||
portName: 'Port Nimara',
|
||||
crmUrl: 'https://crm.portnimara.com/port-nimara/documents/xyz',
|
||||
});
|
||||
|
||||
expect(subject).toContain('Acme Holdings');
|
||||
expect(subject.toLowerCase()).toContain('fully signed');
|
||||
expect(html).toContain('all parties');
|
||||
expect(html).toContain('Sales Contract');
|
||||
expect(html).toContain('Acme Holdings');
|
||||
expect(html).toContain('https://crm.portnimara.com/port-nimara/documents/xyz');
|
||||
expect(text).toContain('Acme Holdings');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user