From 13bd5815d9e74a865dc9172a7f9a337afbab0c4f Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 18 Nov 2025 17:59:36 +0000 Subject: [PATCH] fix: email place holder --- apps/remix/app/utils/analyze-ai-recipients.ts | 32 +------------ apps/remix/server/api/ai.ts | 45 +++---------------- apps/remix/server/api/ai.types.ts | 5 +-- 3 files changed, 8 insertions(+), 74 deletions(-) diff --git a/apps/remix/app/utils/analyze-ai-recipients.ts b/apps/remix/app/utils/analyze-ai-recipients.ts index 80677a9c6..3914f16d9 100644 --- a/apps/remix/app/utils/analyze-ai-recipients.ts +++ b/apps/remix/app/utils/analyze-ai-recipients.ts @@ -9,31 +9,6 @@ export type AiRecipient = { signingOrder?: number; }; -const sanitizeEmailLocalPart = (value: string) => { - return value - .toLowerCase() - .replace(/[^a-z0-9]+/g, '.') - .replace(/\.+/g, '.') - .replace(/^\.+|\.+$/g, '') - .slice(0, 32); -}; - -export const createPlaceholderRecipientEmail = ( - name: string, - envelopeId: string, - position: number, -) => { - const normalizedName = sanitizeEmailLocalPart(name); - const baseLocalPart = normalizedName ? `${normalizedName}.${position}` : `recipient-${position}`; - const envelopeSuffix = envelopeId - .replace(/[^a-z0-9]/gi, '') - .toLowerCase() - .slice(0, 6); - const suffix = envelopeSuffix ? `-${envelopeSuffix}` : ''; - - return `${baseLocalPart}${suffix}@documenso.ai`; -}; - export const analyzeRecipientsFromDocument = async (envelopeId: string): Promise => { try { const response = await fetch('/api/ai/analyze-recipients', { @@ -65,7 +40,6 @@ export const ensureRecipientEmails = ( recipients: AiRecipient[], envelopeId: string, ): RecipientForCreation[] => { - let recipientIndex = 1; const allowedRoles: RecipientRole[] = [ RecipientRole.SIGNER, RecipientRole.APPROVER, @@ -73,11 +47,7 @@ export const ensureRecipientEmails = ( ]; return recipients.map((recipient) => { - const email = - recipient.email ?? - createPlaceholderRecipientEmail(recipient.name, envelopeId, recipientIndex); - - recipientIndex += 1; + const email = recipient.email ?? ''; const candidateRole = recipient.role as RecipientRole; const normalizedRole = allowedRoles.includes(candidateRole) diff --git a/apps/remix/server/api/ai.ts b/apps/remix/server/api/ai.ts index 7eaebde2c..a10575143 100644 --- a/apps/remix/server/api/ai.ts +++ b/apps/remix/server/api/ai.ts @@ -227,7 +227,7 @@ const runFormFieldDetection = async ( const prompt = buildFieldDetectionPrompt(recipients); const result = await generateObject({ - model: 'google/gemini-2.5-pro', + model: 'google/gemini-3-pro-preview', output: 'array', schema: ZDetectedFormFieldSchema, messages: [ @@ -284,33 +284,7 @@ const MAX_PAGES_FOR_RECIPIENT_ANALYSIS = 3; const recipientEmailSchema = z.string().email(); -const sanitizeEmailLocalPart = (value: string) => { - return value - .toLowerCase() - .replace(/[^a-z0-9]+/g, '.') - .replace(/\.+/g, '.') - .replace(/^\.+|\.+$/g, '') - .slice(0, 32); -}; - -const createPlaceholderRecipientEmail = (name: string, envelopeId: string, position: number) => { - const normalizedName = sanitizeEmailLocalPart(name); - const baseLocalPart = normalizedName ? `${normalizedName}.${position}` : `recipient-${position}`; - const sanitizedEnvelopeSuffix = envelopeId - .replace(/[^a-z0-9]/gi, '') - .toLowerCase() - .slice(0, 6); - const suffix = sanitizedEnvelopeSuffix ? `-${sanitizedEnvelopeSuffix}` : ''; - - return `${baseLocalPart}${suffix}@documenso.ai`; -}; - -const resolveRecipientEmail = ( - candidateEmail: string | undefined, - name: string, - envelopeId: string, - position: number, -) => { +const resolveRecipientEmail = (candidateEmail: string | undefined) => { if (candidateEmail) { const trimmedEmail = candidateEmail.trim(); @@ -319,7 +293,7 @@ const resolveRecipientEmail = ( } } - return createPlaceholderRecipientEmail(name, envelopeId, position); + return undefined; }; const authorizeDocumentAccess = async (envelopeId: string, userId: number) => { @@ -388,13 +362,11 @@ EXTRACTION RULES: 3. Look for "Approved by:", "Reviewed by:", "CC:" sections 4. Extract FULL NAMES as they appear in the document 5. If an email address is visible near a name, include it exactly in the "email" field -6. If NO email is found, create a realistic placeholder email using the person's name and the domain "documenso.ai" (e.g., john.doe@documenso.ai). Every recipient MUST have an email. -7. Ensure placeholder emails look unique when multiple people share the same name (append a number if needed) -8. Assign signing order based on document flow (numbered items, "First signer:", "Second signer:", or top-to-bottom sequence) +6. If NO email is found, leave the email field empty. +7. Assign signing order based on document flow (numbered items, "First signer:", "Second signer:", or top-to-bottom sequence) IMPORTANT: - Only extract recipients explicitly mentioned in the document -- Email is mandatory for every recipient (real, sample, or placeholder derived from the document text) - Default role is SIGNER if unclear (signature lines = SIGNER) - Signing order starts at 1 (first signer = 1, second = 2, etc.) - If no clear ordering, omit signingOrder @@ -710,12 +682,7 @@ export const aiRoute = new Hono() const { pageNumber, recipients } = result.value; const recipientsWithEmails = recipients.map((recipient) => { - const email = resolveRecipientEmail( - recipient.email, - recipient.name, - envelopeId, - recipientIndex, - ); + const email = resolveRecipientEmail(recipient.email); const normalizedRecipient: TDetectedRecipient = { ...recipient, diff --git a/apps/remix/server/api/ai.types.ts b/apps/remix/server/api/ai.types.ts index cfd581415..d8741dffa 100644 --- a/apps/remix/server/api/ai.types.ts +++ b/apps/remix/server/api/ai.types.ts @@ -84,10 +84,7 @@ export const ZDetectedRecipientLLMSchema = createRecipientSchema( ); export const ZDetectedRecipientSchema = createRecipientSchema( - z - .string() - .email() - .describe('Email address for the recipient (real, sample, or generated placeholder).'), + z.string().email().optional().describe('Email address for the recipient (if found in document).'), ); export const ZAnalyzeRecipientsRequestSchema = z.object({