fix: add missing email reply validation (#1934)

## Description

General fixes to the email domain features

Changes made:
- Add "email" validation for "Reply-To email" fields
- Fix issue where you can't remove the "Reply-To" email after it's set
- Fix issue where setting the "Sender email" back to Documenso would
still send using the org/team pref
This commit is contained in:
David Nguyen
2025-08-02 00:40:41 +10:00
committed by GitHub
parent 1e2388519c
commit c48486472a
21 changed files with 28 additions and 21 deletions

View File

@ -289,7 +289,7 @@ export const DocumentEditForm = ({
message, message,
distributionMethod, distributionMethod,
emailId, emailId,
emailReplyTo, emailReplyTo: emailReplyTo || null,
emailSettings: emailSettings, emailSettings: emailSettings,
}, },
}); });

View File

@ -143,6 +143,7 @@ export const TemplateEditForm = ({
}, },
meta: { meta: {
...data.meta, ...data.meta,
emailReplyTo: data.meta.emailReplyTo || null,
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE), typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD), uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW), drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),

View File

@ -171,7 +171,7 @@ export default function OrganisationEmailDomainSettingsPage({ params }: Route.Co
<OrganisationEmailDomainRecordsDialog <OrganisationEmailDomainRecordsDialog
records={records} records={records}
trigger={ trigger={
<Button variant="secondary"> <Button variant="outline">
<Trans>View DNS Records</Trans> <Trans>View DNS Records</Trans>
</Button> </Button>
} }

View File

@ -48,7 +48,7 @@ export const run = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const { documentMeta, user: documentOwner } = document; const { documentMeta, user: documentOwner } = document;

View File

@ -76,7 +76,7 @@ export const run = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000'; const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';

View File

@ -68,7 +68,7 @@ export const run = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const i18n = await getI18nInstance(emailLanguage); const i18n = await getI18nInstance(emailLanguage);

View File

@ -86,7 +86,7 @@ export const run = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const customEmail = document?.documentMeta; const customEmail = document?.documentMeta;

View File

@ -156,7 +156,7 @@ const handleDocumentOwnerDelete = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
// Soft delete completed documents. // Soft delete completed documents.

View File

@ -102,7 +102,7 @@ export const resendDocument = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
await Promise.all( await Promise.all(

View File

@ -59,7 +59,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const { user: owner } = document; const { user: owner } = document;

View File

@ -49,7 +49,7 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const { email, name } = document.user; const { email, name } = document.user;

View File

@ -51,7 +51,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const isDocumentPendingEmailEnabled = extractDerivedDocumentEmailSettings( const isDocumentPendingEmailEnabled = extractDerivedDocumentEmailSettings(

View File

@ -46,7 +46,7 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const { status, user } = document; const { status, user } = document;

View File

@ -59,7 +59,7 @@ type RecipientGetEmailContextOptions = BaseGetEmailContextOptions & {
* Force meta options as a typesafe way to ensure developers don't forget to * Force meta options as a typesafe way to ensure developers don't forget to
* pass it in if it is available. * pass it in if it is available.
*/ */
meta: EmailMetaOption | null; meta: EmailMetaOption | null | undefined;
}; };
type GetEmailContextOptions = InternalGetEmailContextOptions | RecipientGetEmailContextOptions; type GetEmailContextOptions = InternalGetEmailContextOptions | RecipientGetEmailContextOptions;
@ -104,7 +104,7 @@ export const getEmailContext = async (
} }
const replyToEmail = meta?.emailReplyTo || emailContext.settings.emailReplyTo || undefined; const replyToEmail = meta?.emailReplyTo || emailContext.settings.emailReplyTo || undefined;
const senderEmailId = meta?.emailId || emailContext.settings.emailId; const senderEmailId = meta?.emailId === null ? null : emailContext.settings.emailId;
const foundSenderEmail = emailContext.allowedEmails.find((email) => email.id === senderEmailId); const foundSenderEmail = emailContext.allowedEmails.find((email) => email.id === senderEmailId);

View File

@ -130,7 +130,7 @@ export const deleteDocumentRecipient = async ({
type: 'team', type: 'team',
teamId: document.teamId, teamId: document.teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const [html, text] = await Promise.all([ const [html, text] = await Promise.all([

View File

@ -95,7 +95,7 @@ export const setDocumentRecipients = async ({
type: 'team', type: 'team',
teamId, teamId,
}, },
meta: document.documentMeta || null, meta: document.documentMeta,
}); });
const recipientsHaveActionAuth = recipients.some( const recipientsHaveActionAuth = recipients.some(

View File

@ -295,7 +295,7 @@ export const ZDistributeDocumentRequestSchema = z.object({
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(), redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(), language: ZDocumentMetaLanguageSchema.optional(),
emailId: z.string().nullish(), emailId: z.string().nullish(),
emailReplyTo: z.string().nullish(), emailReplyTo: z.string().email().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(), emailSettings: ZDocumentEmailSettingsSchema.optional(),
}) })
.optional(), .optional(),

View File

@ -62,7 +62,7 @@ export const ZUpdateDocumentRequestSchema = z.object({
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(), uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(), drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
emailId: z.string().nullish(), emailId: z.string().nullish(),
emailReplyTo: z.string().nullish(), emailReplyTo: z.string().email().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(), emailSettings: ZDocumentEmailSettingsSchema.optional(),
}) })
.optional(), .optional(),

View File

@ -65,7 +65,7 @@ export const ZTemplateMetaUpsertSchema = z.object({
dateFormat: ZDocumentMetaDateFormatSchema.optional(), dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(), distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
emailId: z.string().nullish(), emailId: z.string().nullish(),
emailReplyTo: z.string().nullish(), emailReplyTo: z.string().email().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(), emailSettings: ZDocumentEmailSettingsSchema.optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(), redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(), language: ZDocumentMetaLanguageSchema.optional(),

View File

@ -6,7 +6,10 @@ import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-emai
export const ZAddSubjectFormSchema = z.object({ export const ZAddSubjectFormSchema = z.object({
meta: z.object({ meta: z.object({
emailId: z.string().nullable(), emailId: z.string().nullable(),
emailReplyTo: z.string().email().optional(), emailReplyTo: z.preprocess(
(val) => (val === '' ? undefined : val),
z.string().email().optional(),
),
// emailReplyName: z.string().optional(), // emailReplyName: z.string().optional(),
subject: z.string(), subject: z.string(),
message: z.string(), message: z.string(),

View File

@ -49,7 +49,10 @@ export const ZAddTemplateSettingsFormSchema = z.object({
.optional() .optional()
.default('en'), .default('en'),
emailId: z.string().nullable(), emailId: z.string().nullable(),
emailReplyTo: z.string().optional(), emailReplyTo: z.preprocess(
(val) => (val === '' ? undefined : val),
z.string().email().optional(),
),
emailSettings: ZDocumentEmailSettingsSchema, emailSettings: ZDocumentEmailSettingsSchema,
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, { signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
message: msg`At least one signature type must be enabled`.id, message: msg`At least one signature type must be enabled`.id,