fix(eoi): silence Documenso's own lifecycle emails on createDocument
The local-fill EOI pathway creates fresh Documenso envelopes via createDocument, which (unlike the template pathway that inherits template 8's all-false emailSettings) used Documenso's defaults — every email event defaults to true on both the v1 and v2.13 APIs. So Documenso fired its OWN unbranded "Waiting for others to complete signing." and "Signing Complete!" emails (signed PDF attached, reply-to sales@), bypassing EMAIL_REDIRECT_TO and duplicating the CRM's branded sends. Force emailSettings to all-false (DOCUMENSO_SILENT_EMAIL_SETTINGS) on every createDocument call (v1 JSON + v2 multipart). The CRM stays the sole sender of signing comms. Verified against the live v2.13 OpenAPI + template 8's stored meta. Also stop the EMAIL_REDIRECT_TO gate from appending "(was: <email>)" to the recipient NAME: a "Name" field auto-fills from it into the signed PDF, so the annotation overlapped the signature. Redirect the email only; the original is still logged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -243,16 +243,19 @@ export interface DocumensoDocument {
|
||||
/**
|
||||
* When EMAIL_REDIRECT_TO is set (dev / staging), rewrite every recipient
|
||||
* email so Documenso doesn't accidentally email real clients during a
|
||||
* data import / migration dry-run. Names are prefixed with the original
|
||||
* email so the recipient (you) can tell who would have received the doc.
|
||||
* data import / migration dry-run.
|
||||
*
|
||||
* In production this env var is unset and recipients flow through unchanged.
|
||||
* The NAME is left untouched: a "Name" signature field auto-fills from the
|
||||
* recipient name and renders into the signed PDF, so any annotation here
|
||||
* (we used to append "(was: <email>)") leaks into the document and overlaps
|
||||
* the signature. The original email is captured in the createDocument log
|
||||
* line instead. In production this env var is unset and recipients flow
|
||||
* through unchanged.
|
||||
*/
|
||||
function applyRecipientRedirect(recipients: DocumensoRecipient[]): DocumensoRecipient[] {
|
||||
if (!env.EMAIL_REDIRECT_TO) return recipients;
|
||||
return recipients.map((r) => ({
|
||||
...r,
|
||||
name: `${r.name} (was: ${r.email})`,
|
||||
email: env.EMAIL_REDIRECT_TO!,
|
||||
}));
|
||||
}
|
||||
@@ -265,11 +268,11 @@ function applyRecipientRedirect(recipients: DocumensoRecipient[]): DocumensoReci
|
||||
function applyPayloadRedirect(payload: Record<string, unknown>): Record<string, unknown> {
|
||||
if (!env.EMAIL_REDIRECT_TO) return payload;
|
||||
const out: Record<string, unknown> = { ...payload };
|
||||
// 2.x recipient shape
|
||||
// 2.x recipient shape — redirect the email only, keep the name clean (it
|
||||
// renders into the signed PDF's Name field). See applyRecipientRedirect.
|
||||
if (Array.isArray(out.recipients)) {
|
||||
out.recipients = (out.recipients as Array<Record<string, unknown>>).map((r) => ({
|
||||
...r,
|
||||
name: `${String(r.name ?? '')} (was: ${String(r.email ?? '')})`,
|
||||
email: env.EMAIL_REDIRECT_TO,
|
||||
}));
|
||||
}
|
||||
@@ -288,11 +291,41 @@ function applyPayloadRedirect(payload: Record<string, unknown>): Record<string,
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Documenso fires its OWN lifecycle emails for every envelope: each event
|
||||
* below defaults to `true` (verified against the v2.13 OpenAPI + the EOI
|
||||
* Documenso template's stored meta). The CRM is the SOLE sender of signing
|
||||
* comms — branded invitations via `sendSigningInvitation`, plus the
|
||||
* completion / "who signed" alert emails — so we disable ALL of Documenso's
|
||||
* events at creation time.
|
||||
*
|
||||
* Without this, the local-fill pathway (which creates fresh envelopes via
|
||||
* `createDocument`, unlike the template pathway that inherits the template's
|
||||
* all-false `emailSettings`) leaks unbranded "Waiting for others" /
|
||||
* "Signing Complete!" emails — sent with the signed PDF attached from the
|
||||
* Documenso instance's own account (reply-to sales@) — duplicating ours.
|
||||
*
|
||||
* The v2 schema marks every key `required` when the object is present, so
|
||||
* all nine are listed explicitly.
|
||||
*/
|
||||
export const DOCUMENSO_SILENT_EMAIL_SETTINGS = {
|
||||
recipientSigningRequest: false,
|
||||
recipientRemoved: false,
|
||||
recipientSigned: false,
|
||||
documentPending: false,
|
||||
documentCompleted: false,
|
||||
documentDeleted: false,
|
||||
ownerDocumentCompleted: false,
|
||||
ownerRecipientExpired: false,
|
||||
ownerDocumentCreated: false,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Optional metadata applied to the document on creation. v1 accepts
|
||||
* `redirectUrl` and `subject`/`message` on its `/documents` endpoint.
|
||||
* v2's `/envelope/create` accepts the same plus `signingOrder` for
|
||||
* PARALLEL-vs-SEQUENTIAL signing enforcement.
|
||||
* PARALLEL-vs-SEQUENTIAL signing enforcement. `emailSettings` is always
|
||||
* forced to `DOCUMENSO_SILENT_EMAIL_SETTINGS` inside `createDocument`.
|
||||
*/
|
||||
export interface CreateDocumentMeta {
|
||||
subject?: string;
|
||||
@@ -342,16 +375,14 @@ export async function createDocument(
|
||||
role: r.role,
|
||||
signingOrder: r.signingOrder || i + 1,
|
||||
})),
|
||||
...(meta
|
||||
? {
|
||||
meta: {
|
||||
...(meta.subject ? { subject: meta.subject } : {}),
|
||||
...(meta.message ? { message: meta.message } : {}),
|
||||
...(meta.redirectUrl ? { redirectUrl: meta.redirectUrl } : {}),
|
||||
...(meta.signingOrder ? { signingOrder: meta.signingOrder } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
meta: {
|
||||
// CRM is the sole email sender — Documenso stays silent.
|
||||
emailSettings: DOCUMENSO_SILENT_EMAIL_SETTINGS,
|
||||
...(meta?.subject ? { subject: meta.subject } : {}),
|
||||
...(meta?.message ? { message: meta.message } : {}),
|
||||
...(meta?.redirectUrl ? { redirectUrl: meta.redirectUrl } : {}),
|
||||
...(meta?.signingOrder ? { signingOrder: meta.signingOrder } : {}),
|
||||
},
|
||||
};
|
||||
form.append('payload', JSON.stringify(payload));
|
||||
form.append(
|
||||
@@ -412,15 +443,13 @@ export async function createDocument(
|
||||
title,
|
||||
document: pdfBase64,
|
||||
recipients: safeRecipients,
|
||||
...(meta?.subject || meta?.message || meta?.redirectUrl
|
||||
? {
|
||||
meta: {
|
||||
...(meta.subject ? { subject: meta.subject } : {}),
|
||||
...(meta.message ? { message: meta.message } : {}),
|
||||
...(meta.redirectUrl ? { redirectUrl: meta.redirectUrl } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
meta: {
|
||||
// CRM is the sole email sender — Documenso stays silent.
|
||||
emailSettings: DOCUMENSO_SILENT_EMAIL_SETTINGS,
|
||||
...(meta?.subject ? { subject: meta.subject } : {}),
|
||||
...(meta?.message ? { message: meta.message } : {}),
|
||||
...(meta?.redirectUrl ? { redirectUrl: meta.redirectUrl } : {}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
portId,
|
||||
|
||||
Reference in New Issue
Block a user