feat: allow omitting document PDFs from emails

This commit is contained in:
ephraimduncan
2026-09-30 21:02:42 +00:00
parent f4105e47eb
commit b4f5238206
36 changed files with 761 additions and 46 deletions
@@ -569,6 +569,32 @@ const document = await response.json();
</Tab>
</Tabs>
### Omit document PDFs from email
Set `meta.emailSettings.attachDocument` to `false` when creating or updating a document or template. Completion emails still go to the enabled recipients and owner, and retain their document links. This also omits PDFs from CC completion emails.
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `[meta.emailSettings.attachDocument]` | boolean | `true` | Attach completed document PDFs to emails. Set to `false` for links only. |
For example, update an envelope with this request body:
```json
{
"envelopeId": "envelope_abc123",
"meta": {
"emailSettings": {
"attachDocument": false
}
}
}
```
When using a template with `/envelope/use`, pass the option in `override.emailSettings`. The document and template APIs, including API v1, accept the same boolean option in their email settings. Non-boolean values are rejected.
If you omit the email settings on creation, the document inherits the team's defaults, which can inherit from the organisation. Existing documents and templates retain their saved settings when those defaults change. If you provide an email settings object without `attachDocument`, attachments stay enabled for backward compatibility.
---
## Send Document
@@ -48,6 +48,12 @@ Or you can customize it before sending the document, by clicking on the **Docume
![Editor email settings](/document-signing/documenso-editor-email-settings.webp)
### Document PDF attachments
In **Document Settings > Notifications**, clear **Attach completed document PDFs to emails** to send completion emails with document links only. This applies to the owner and all recipients, including CCs. The email notifications remain enabled, and recipients can access the document through their links.
PDF attachments are enabled by default. Signing invitations and pending emails already include links without document PDFs. You can also set the attachment default for new documents and templates in your [email preferences](/docs/users/organisations/preferences/email).
### Reply-to email (optional)
Set a reply-to address so recipient replies go to a specific email instead of the default.
@@ -25,6 +25,12 @@ To access the preferences, navigate to either the organisation or teams settings
| **Reply to email** | Email address used in the "Reply To" field in outgoing emails. Leave blank to inherit from the organisation. |
| **Default Email Settings** | Controls which emails are sent to recipients during document signing (e.g. signing invitations, completion notifications). Teams can inherit from the organisation or override with custom settings. |
### Document PDF attachments
Under **Default Email Settings**, clear **Attach completed document PDFs to emails** to omit PDFs from completion emails to the owner and all recipients, including CCs. Emails still include document links. Attachments are enabled by default.
Teams inherit this preference with the other email settings. Select **Override organisation settings** to configure it for a team, or **Inherit from organisation** to restore inheritance. Changes apply to new documents and templates; existing documents and templates keep their saved settings. You can change the attachment setting for an individual document or template in **Document Settings > Notifications**.
## See Also
- [Email Domains](/docs/users/organisations/email-domains) - Send emails from your own domain
@@ -10,6 +10,7 @@ import type { ReactNode } from 'react';
import { DetailsCard, DetailsValue } from '~/components/general/admin-details';
const EMAIL_SETTINGS_LABELS: Record<keyof TDocumentEmailSettings, MessageDescriptor> = {
attachDocument: msg`Attach completed document PDFs`,
recipientSigningRequest: msg`Recipient signing request`,
recipientRemoved: msg`Recipient removed`,
recipientSigned: msg`Recipient signed`,
@@ -35,7 +35,7 @@ export default function OrganisationSettingsGeneral() {
emailId,
emailReplyTo: emailReplyTo || null,
// emailReplyToName,
emailDocumentSettings,
emailDocumentSettings: emailDocumentSettings ?? undefined,
includeSenderDetails: includeSenderDetails ?? undefined,
},
});
+3 -3
View File
@@ -8,7 +8,7 @@ import {
ZDocumentActionAuthTypesSchema,
ZRecipientActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
import { ZFieldMetaPrefillFieldsSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { ZRecipientSigningOrderSchema } from '@documenso/lib/types/recipient';
@@ -172,7 +172,7 @@ export const ZCreateDocumentMutationSchema = z.object({
uploadSignatureEnabled: z.boolean().optional().default(true),
drawSignatureEnabled: z.boolean().optional().default(true),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.partial()
.optional()
@@ -341,7 +341,7 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
typedSignatureEnabled: z.boolean(),
uploadSignatureEnabled: z.boolean(),
drawSignatureEnabled: z.boolean(),
emailSettings: ZDocumentEmailSettingsSchema,
emailSettings: ZDocumentEmailSettingsInputSchema,
})
.partial()
.optional(),
@@ -0,0 +1,85 @@
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { expect, test } from '@playwright/test';
import {
addEnvelopeItemPdf,
getEnvelopeEditorSettingsTrigger,
openDocumentEnvelopeEditor,
openEmbeddedEnvelopeEditor,
openTemplateEnvelopeEditor,
persistEmbeddedEnvelope,
setRecipientEmail,
type TEnvelopeEditorSurface,
} from '../fixtures/envelope-editor';
import { expectToastTextToBeVisible } from '../fixtures/generic';
const runAttachmentSettingsFlow = async (surface: TEnvelopeEditorSurface) => {
const { root, isEmbedded } = surface;
const externalId = `e2e-email-attachments-${nanoid()}`;
if (isEmbedded && !surface.envelopeId) {
await addEnvelopeItemPdf(root);
await setRecipientEmail(root, 0, 'signer@example.com');
}
for (const attachDocument of [false, true, false]) {
await getEnvelopeEditorSettingsTrigger(root).click();
await root.locator('input[name="externalId"]').fill(externalId);
await root.getByRole('button', { name: 'Notifications' }).click();
const attachmentControl = root.getByRole('checkbox', { name: 'Attach completed document PDFs to emails' });
await expect(attachmentControl).toBeChecked({ checked: !attachDocument });
await attachmentControl.setChecked(attachDocument);
await expect(root.locator('#documentCompleted')).toBeChecked();
await expect(root.locator('#ownerDocumentCompleted')).toBeChecked();
await root.getByRole('button', { name: 'Update', exact: true }).click();
if (!isEmbedded) {
await expectToastTextToBeVisible(root, 'Envelope updated');
const meta = await prisma.envelope.findUniqueOrThrow({
where: { id: surface.envelopeId },
include: { documentMeta: true },
});
expect(meta.documentMeta.emailSettings).toMatchObject({ attachDocument });
await root.reload();
}
}
if (isEmbedded) {
await persistEmbeddedEnvelope(surface);
}
const envelope = await prisma.envelope.findFirstOrThrow({
where: { externalId, userId: surface.userId, teamId: surface.teamId, type: surface.envelopeType },
include: { documentMeta: true },
});
expect(envelope.documentMeta.emailSettings).toMatchObject({
attachDocument: false,
documentCompleted: true,
ownerDocumentCompleted: true,
});
};
test.describe('document editor', () => {
test('save email attachment opt-out and toggle back', async ({ page }) => {
await runAttachmentSettingsFlow(await openDocumentEnvelopeEditor(page));
});
});
test.describe('template editor', () => {
test('save email attachment opt-out and toggle back', async ({ page }) => {
await runAttachmentSettingsFlow(await openTemplateEnvelopeEditor(page));
});
});
test.describe('embedded create', () => {
test('save email attachment opt-out and toggle back', async ({ page }) => {
await runAttachmentSettingsFlow(await openEmbeddedEnvelopeEditor(page, { envelopeType: 'DOCUMENT' }));
});
});
test.describe('embedded edit', () => {
test('save email attachment opt-out and toggle back', async ({ page }) => {
await runAttachmentSettingsFlow(await openEmbeddedEnvelopeEditor(page, { envelopeType: 'TEMPLATE', mode: 'edit' }));
});
});
@@ -215,6 +215,8 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
await page.getByRole('textbox', { name: 'Reply to email' }).click();
await page.getByRole('textbox', { name: 'Reply to email' }).fill('organisation@documenso.com');
await page.getByRole('checkbox', { name: 'Attach completed document PDFs to emails' }).uncheck();
// Update email document settings by enabling/disabling some checkboxes
await page.getByRole('checkbox', { name: 'Email the owner when a recipient signs' }).uncheck();
await page.getByRole('checkbox', { name: 'Email the signer if the document is still pending' }).uncheck();
@@ -230,6 +232,7 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
// Check that the team settings have inherited these values.
expect(teamSettings.emailReplyTo).toEqual('organisation@documenso.com');
expect(teamSettings.emailDocumentSettings).toEqual({
attachDocument: false,
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: false, // unchecked
@@ -273,6 +276,7 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
// Check that the team settings have overridden the organisation values.
expect(updatedTeamSettings.emailReplyTo).toEqual('team@example.com');
expect(updatedTeamSettings.emailDocumentSettings).toEqual({
attachDocument: true,
recipientSigned: true,
recipientSigningRequest: false,
recipientRemoved: true,
@@ -295,6 +299,7 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
expect(teamOverrideDocumentMeta.emailReplyTo).toEqual('team@example.com');
expect(teamOverrideDocumentMeta.emailSettings).toEqual({
attachDocument: true,
recipientSigned: true,
recipientSigningRequest: false,
recipientRemoved: true,
@@ -322,6 +327,7 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
// Check that the team settings now inherit from organisation again.
expect(inheritedTeamSettings.emailReplyTo).toEqual('organisation@documenso.com');
expect(inheritedTeamSettings.emailDocumentSettings).toEqual({
attachDocument: false,
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: false,
@@ -344,6 +350,7 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
expect(documentMeta.emailReplyTo).toEqual('organisation@documenso.com');
expect(documentMeta.emailSettings).toEqual({
attachDocument: false,
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: false,
@@ -354,4 +361,22 @@ test('[ORGANISATIONS]: manage email preferences', async ({ page }) => {
ownerDocumentCompleted: true,
ownerDocumentCreated: true,
});
// Re-enable attachments and verify that inherited defaults change only for new documents.
await page.goto(`/o/${organisation.url}/settings/email`);
const attachmentControl = page.getByRole('checkbox', { name: 'Attach completed document PDFs to emails' });
await expect(attachmentControl).not.toBeChecked();
await attachmentControl.check();
await page.getByRole('button', { name: 'Save changes' }).first().click();
await expect(page.getByText('Your email preferences have been updated').first()).toBeVisible();
await page.reload();
await expect(attachmentControl).toBeChecked();
const restoredSettings = await getTeamSettings({ teamId: team.id });
expect(restoredSettings.emailDocumentSettings.attachDocument).toBe(true);
const newDocument = await seedTeamDocumentWithMeta(team);
const newMeta = await prisma.documentMeta.findUniqueOrThrow({ where: { id: newDocument.documentMetaId } });
expect(newMeta.emailSettings?.attachDocument).toBe(true);
const existingMeta = await prisma.documentMeta.findUniqueOrThrow({ where: { id: document.documentMetaId } });
expect(existingMeta.emailSettings?.attachDocument).toBe(false);
});
@@ -0,0 +1,190 @@
import { DocumentDistributionMethod, DocumentSource, RecipientRole } from '@prisma/client';
import type { ReactElement } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_DOCUMENT_EMAIL_SETTINGS } from '../../../types/document-email';
import type { JobRunIO } from '../../client/_internal/job';
import { run } from './send-document-completed-emails.handler';
const mocks = vi.hoisted(() => ({
findEnvelope: vi.fn(),
createAuditLog: vi.fn(),
findEnvelopeItems: vi.fn(),
getEmailContext: vi.fn(),
getFile: vi.fn(),
sendMail: vi.fn(),
checkLimits: vi.fn(),
}));
vi.mock('@documenso/prisma', () => ({
prisma: {
envelope: { findUnique: mocks.findEnvelope },
envelopeItem: { findMany: mocks.findEnvelopeItems },
documentAuditLog: { create: mocks.createAuditLog },
},
}));
vi.mock('@documenso/email/templates/document-completed', () => ({
DocumentCompletedEmailTemplate: () => null,
}));
vi.mock('../../../client-only/providers/i18n-server', () => ({
getI18nInstance: async () => ({ _: () => 'Signing Complete!' }),
}));
vi.mock('../../../constants/app', () => ({ NEXT_PUBLIC_WEBAPP_URL: () => 'http://localhost:3000' }));
vi.mock('../../../server-only/email/get-email-context', () => ({ getEmailContext: mocks.getEmailContext }));
vi.mock('../../../server-only/rate-limit/assert-organisation-rates-and-limits', () => ({
assertOrganisationRatesAndLimits: mocks.checkLimits,
}));
vi.mock('../../../universal/upload/get-file.server', () => ({ getFileServerSide: mocks.getFile }));
vi.mock('../../../utils/document-audit-logs', () => ({ createDocumentAuditLogData: (data: unknown) => data }));
vi.mock('../../../utils/teams', () => ({ formatDocumentsPath: () => '/t/test-team/documents' }));
vi.mock('../../../utils/render-email-with-i18n', () => ({
renderEmailWithI18N: async (template: ReactElement<{ downloadLink: string }>) => template.props.downloadLink,
}));
const createEnvelope = (distributionMethod: DocumentDistributionMethod = DocumentDistributionMethod.EMAIL) => ({
id: 'envelope_test',
secondaryId: 'document_1',
teamId: 1,
internalVersion: 2,
title: 'Confidential document',
source: DocumentSource.DOCUMENT,
team: { id: 1, url: 'test-team' },
user: { id: 1, name: 'Owner', email: 'owner@example.com', disabled: false },
documentMeta: {
distributionMethod,
emailSettings: { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS },
},
envelopeItems: [
{ title: 'First document', documentData: { id: 'file_1', type: 'S3', data: 'file_1' } },
{ title: 'Second document', documentData: { id: 'file_2', type: 'BYTES', data: 'file_2' } },
],
recipients: [
{ id: 2, name: 'Signer', email: 'signer@example.com', role: RecipientRole.SIGNER, token: 'signer-token' },
{ id: 3, name: 'CC', email: 'cc@example.com', role: RecipientRole.CC, token: 'cc-token' },
],
});
let envelope = createEnvelope();
const io = { logger: { warn: vi.fn() } } as unknown as JobRunIO;
const runJob = () => run({ payload: { envelopeId: envelope.id }, io });
beforeEach(() => {
vi.clearAllMocks();
envelope = createEnvelope();
mocks.findEnvelope.mockImplementation(async () => envelope);
mocks.findEnvelopeItems.mockImplementation(async () => envelope.envelopeItems);
mocks.getFile.mockResolvedValue(new Uint8Array([1, 2, 3]));
mocks.getEmailContext.mockResolvedValue({
branding: {},
emailLanguage: 'en',
senderEmail: 'sender@example.com',
replyToEmail: 'reply@example.com',
organisationId: 'org_test',
claims: {},
emailsDisabled: false,
emailTransport: { sendMail: mocks.sendMail },
});
});
describe('completion email document attachments', () => {
it('keeps email content, links, audit logs, and CC metering when PDFs are disabled', async () => {
envelope.documentMeta.emailSettings.attachDocument = false;
// Loading a PDF would fail: link-only email must still succeed.
mocks.getFile.mockRejectedValue(new Error('PDF storage unavailable'));
await runJob();
expect(mocks.findEnvelopeItems).not.toHaveBeenCalled();
expect(mocks.getFile).not.toHaveBeenCalled();
expect(mocks.sendMail).toHaveBeenCalledTimes(3);
const emails = mocks.sendMail.mock.calls.map(([email]) => email);
expect(emails.map((email) => email.to[0].address)).toEqual([
'owner@example.com',
'signer@example.com',
'cc@example.com',
]);
for (const email of emails) {
expect(email).toMatchObject({ attachments: [], subject: 'Signing Complete!' });
expect(email.html).toBe(email.text);
}
expect(emails[0].html).toBe('http://localhost:3000/t/test-team/documents/envelope_test');
expect(emails[1].html).toBe('http://localhost:3000/sign/signer-token/complete');
expect(emails[2].html).toBe('http://localhost:3000/sign/cc-token/complete');
expect(mocks.createAuditLog).toHaveBeenCalledTimes(3);
expect(mocks.checkLimits).toHaveBeenCalledOnce();
});
it.each([true, undefined])('attaches all PDFs by default and when enabled (%s)', async (attachDocument) => {
mocks.findEnvelope.mockResolvedValue({
...envelope,
documentMeta: { ...envelope.documentMeta, emailSettings: { attachDocument } },
});
await runJob();
expect(mocks.getFile).toHaveBeenCalledTimes(2);
expect(mocks.sendMail).toHaveBeenCalledTimes(3);
for (const [email] of mocks.sendMail.mock.calls) {
expect(email.attachments).toEqual([
{ filename: 'First document.pdf', content: Buffer.from([1, 2, 3]), contentType: 'application/pdf' },
{ filename: 'Second document.pdf', content: Buffer.from([1, 2, 3]), contentType: 'application/pdf' },
]);
}
});
it('retains legacy single-document filenames', async () => {
envelope.internalVersion = 1;
envelope.envelopeItems = [envelope.envelopeItems[0]];
await runJob();
expect(mocks.sendMail.mock.calls[0][0].attachments[0].filename).toBe('Confidential document.pdf');
});
it('sends link-only owner emails when recipient emails are disabled', async () => {
envelope.documentMeta.emailSettings.attachDocument = false;
envelope.documentMeta.emailSettings.documentCompleted = false;
await runJob();
expect(mocks.findEnvelopeItems).not.toHaveBeenCalled();
expect(mocks.getFile).not.toHaveBeenCalled();
expect(mocks.sendMail).toHaveBeenCalledOnce();
expect(mocks.sendMail.mock.calls[0][0].to[0].address).toBe('owner@example.com');
});
it('sends link-only recipient emails when owner emails are disabled', async () => {
envelope.documentMeta.emailSettings.attachDocument = false;
envelope.documentMeta.emailSettings.ownerDocumentCompleted = false;
await runJob();
expect(mocks.findEnvelopeItems).not.toHaveBeenCalled();
expect(mocks.getFile).not.toHaveBeenCalled();
expect(mocks.sendMail).toHaveBeenCalledTimes(2);
});
it('sends a single link-only completion email when the owner is also a recipient', async () => {
envelope.documentMeta.emailSettings.attachDocument = false;
envelope.recipients[0].email = envelope.user.email;
await runJob();
expect(mocks.sendMail).toHaveBeenCalledTimes(2);
expect(mocks.sendMail.mock.calls[0][0]).toMatchObject({
to: [{ address: 'owner@example.com' }],
attachments: [],
html: 'http://localhost:3000/t/test-team/documents/envelope_test',
});
});
it('preserves owner-only link emails when distribution is NONE', async () => {
envelope.documentMeta.distributionMethod = DocumentDistributionMethod.NONE;
envelope.documentMeta.emailSettings.attachDocument = false;
await runJob();
expect(mocks.findEnvelopeItems).not.toHaveBeenCalled();
expect(mocks.getFile).not.toHaveBeenCalled();
expect(mocks.sendMail).toHaveBeenCalledOnce();
});
it('does not load PDFs or send mail when both completion emails are disabled', async () => {
envelope.documentMeta.emailSettings.documentCompleted = false;
envelope.documentMeta.emailSettings.ownerDocumentCompleted = false;
await runJob();
expect(mocks.findEnvelopeItems).not.toHaveBeenCalled();
expect(mocks.getFile).not.toHaveBeenCalled();
expect(mocks.sendMail).not.toHaveBeenCalled();
});
});
@@ -26,17 +26,6 @@ export const run = async ({ payload, io }: { payload: TSendDocumentCompletedEmai
const envelope = await prisma.envelope.findUnique({
where: unsafeBuildEnvelopeIdQuery({ type: 'envelopeId', id: envelopeId }, EnvelopeType.DOCUMENT),
include: {
envelopeItems: {
include: {
documentData: {
select: {
type: true,
id: true,
data: true,
},
},
},
},
documentMeta: true,
recipients: true,
user: {
@@ -83,15 +72,39 @@ export const run = async ({ payload, io }: { payload: TSendDocumentCompletedEmai
const { user: owner } = envelope;
const emailSettings = extractDerivedDocumentEmailSettings(envelope.documentMeta);
const isDocumentCompletedEmailEnabled = emailSettings.documentCompleted;
const isOwnerDocumentCompletedEmailEnabled = emailSettings.ownerDocumentCompleted;
if (!isDocumentCompletedEmailEnabled && !isOwnerDocumentCompletedEmailEnabled) {
return;
}
// Fetch PDF data only when attachments are enabled. Database-backed PDFs can
// contain the entire file in documentData.data.
const envelopeItems = emailSettings.attachDocument
? await prisma.envelopeItem.findMany({
where: { envelopeId: envelope.id },
include: {
documentData: {
select: {
type: true,
data: true,
},
},
},
})
: [];
const completedDocumentEmailAttachments = await Promise.all(
envelope.envelopeItems.map(async (envelopeItem) => {
envelopeItems.map(async (envelopeItem) => {
const file = await getFileServerSide(envelopeItem.documentData);
// Use the envelope title for version 1, and the envelope item title for version 2.
const fileNameToUse = envelope.internalVersion === 1 ? envelope.title : envelopeItem.title + '.pdf';
const fileNameToUse = envelope.internalVersion === 1 ? envelope.title : `${envelopeItem.title}.pdf`;
return {
filename: fileNameToUse.endsWith('.pdf') ? fileNameToUse : fileNameToUse + '.pdf',
filename: fileNameToUse.endsWith('.pdf') ? fileNameToUse : `${fileNameToUse}.pdf`,
content: Buffer.from(file),
contentType: 'application/pdf',
};
@@ -108,10 +121,6 @@ export const run = async ({ payload, io }: { payload: TSendDocumentCompletedEmai
documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${envelope.team.url}/documents/${envelope.id}`;
}
const emailSettings = extractDerivedDocumentEmailSettings(envelope.documentMeta);
const isDocumentCompletedEmailEnabled = emailSettings.documentCompleted;
const isOwnerDocumentCompletedEmailEnabled = emailSettings.ownerDocumentCompleted;
// Send email to document owner if:
// 1. Owner document completed emails are enabled AND
// 2. Either:
@@ -437,6 +437,7 @@ export const generateSampleWebhookPayload = (event: WebhookTriggerEvents, webhoo
dateFormat: 'yyyy-MM-dd hh:mm a',
redirectUrl: '',
emailSettings: {
attachDocument: true,
documentDeleted: true,
documentPending: true,
recipientSigned: true,
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "Ihr Verifizierungscode:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15317,3 +15317,15 @@ msgstr "Your verification code:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr "Attach completed document PDFs"
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr "Attach completed document PDFs to emails"
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "Su código de verificación:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "su-dominio.com otro-dominio.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "Votre code de vérification :"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "Il tuo codice di verifica:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "tuo-dominio.com altro-dominio.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "認証コード:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "인증 코드:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "Uw verificatiecode:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+11
View File
@@ -15359,3 +15359,14 @@ msgstr "Powiększ"
msgid "Zoom out"
msgstr "Pomniejsz"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15317,3 +15317,15 @@ msgstr "Seu código de verificação:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "seu-dominio.com outro-dominio.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
+12
View File
@@ -15322,3 +15322,15 @@ msgstr "您的验证码:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "your-domain.com another-domain.com"
#: apps/remix/app/components/general/admin-global-settings-section.tsx
msgid "Attach completed document PDFs"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Attach completed document PDFs to emails"
msgstr ""
#: packages/ui/components/document/document-email-checkboxes.tsx
msgid "Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable this to send emails with document links only."
msgstr ""
@@ -0,0 +1,101 @@
import { describe, expect, it } from 'vitest';
import { ZCreateDocumentMutationSchema, ZGenerateDocumentFromTemplateMutationSchema } from '../../api/v1/schema';
import { ZCreateDocumentPayloadSchema } from '../../trpc/server/document-router/create-document.types';
import { ZCreateEnvelopePayloadSchema } from '../../trpc/server/envelope-router/create-envelope.types';
import { ZUpdateEnvelopeRequestSchema } from '../../trpc/server/envelope-router/update-envelope.types';
import { ZUseEnvelopePayloadSchema } from '../../trpc/server/envelope-router/use-envelope.types';
import { ZUpdateOrganisationSettingsRequestSchema } from '../../trpc/server/organisation-router/update-organisation-settings.types';
import { ZUpdateTeamSettingsRequestSchema } from '../../trpc/server/team-router/update-team-settings.types';
import {
ZCreateDocumentFromTemplateRequestSchema,
ZCreateTemplateV2RequestSchema,
} from '../../trpc/server/template-router/schema';
const requestCases = [
{
name: 'v1 document creation',
schema: ZCreateDocumentMutationSchema,
body: { title: 'Document', recipients: [] },
field: 'meta',
},
{
name: 'v1 template use',
schema: ZGenerateDocumentFromTemplateMutationSchema,
body: { recipients: [] },
field: 'meta',
},
{ name: 'document creation', schema: ZCreateDocumentPayloadSchema, body: { title: 'Document' }, field: 'meta' },
{
name: 'envelope creation',
schema: ZCreateEnvelopePayloadSchema,
body: { title: 'Document', type: 'DOCUMENT' },
field: 'meta',
},
{
name: 'envelope update',
schema: ZUpdateEnvelopeRequestSchema,
body: { envelopeId: 'envelope_test' },
field: 'meta',
},
{
name: 'envelope template use',
schema: ZUseEnvelopePayloadSchema,
body: { envelopeId: 'envelope_test' },
field: 'override',
},
{ name: 'template creation', schema: ZCreateTemplateV2RequestSchema, body: { title: 'Template' }, field: 'meta' },
{
name: 'document from template',
schema: ZCreateDocumentFromTemplateRequestSchema,
body: { templateId: 1, recipients: [] },
field: 'override',
},
] as const;
describe.each(requestCases)('$name email settings API', ({ schema, body, field }) => {
it.each([true, false])('accepts and retains attachDocument=%s', (attachDocument) => {
const request = { ...body, [field]: { emailSettings: { attachDocument } } };
const result = schema.parse(request);
expect(result).toMatchObject({ [field]: { emailSettings: { attachDocument } } });
});
it('rejects non-boolean attachment controls instead of enabling attachments', () => {
const request = { ...body, [field]: { emailSettings: { attachDocument: 'false' } } };
expect(schema.safeParse(request).success).toBe(false);
});
});
describe('default email settings API', () => {
it.each([true, false])('retains organisation and team attachment defaults (%s)', (attachDocument) => {
expect(
ZUpdateOrganisationSettingsRequestSchema.parse({
organisationId: 'org_test',
data: { emailDocumentSettings: { attachDocument } },
}),
).toMatchObject({ data: { emailDocumentSettings: { attachDocument } } });
expect(
ZUpdateTeamSettingsRequestSchema.parse({ teamId: 1, data: { emailDocumentSettings: { attachDocument } } }),
).toMatchObject({ data: { emailDocumentSettings: { attachDocument } } });
});
it('rejects malformed organisation and team defaults', () => {
expect(
ZUpdateOrganisationSettingsRequestSchema.safeParse({
organisationId: 'org_test',
data: { emailDocumentSettings: { attachDocument: 'false' } },
}).success,
).toBe(false);
expect(
ZUpdateTeamSettingsRequestSchema.safeParse({
teamId: 1,
data: { emailDocumentSettings: { attachDocument: 'false' } },
}).success,
).toBe(false);
});
it('retains null as the team inheritance control', () => {
expect(ZUpdateTeamSettingsRequestSchema.parse({ teamId: 1, data: { emailDocumentSettings: null } })).toMatchObject({
data: { emailDocumentSettings: null },
});
});
});
+94
View File
@@ -0,0 +1,94 @@
import { DocumentDistributionMethod, type DocumentMeta } from '@prisma/client';
import { describe, expect, it } from 'vitest';
import { extractDerivedDocumentMeta } from '../utils/document';
import { generateDefaultOrganisationSettings } from '../utils/organisations';
import { extractDerivedTeamSettings, generateDefaultTeamSettings } from '../utils/teams';
import {
DEFAULT_DOCUMENT_EMAIL_SETTINGS,
extractDerivedDocumentEmailSettings,
ZDocumentEmailSettingsInputSchema,
ZDocumentEmailSettingsSchema,
} from './document-email';
import { ZDocumentMetaCreateSchema, ZDocumentMetaUpdateSchema } from './document-meta';
describe('document email attachments', () => {
it('keeps attachments enabled for absent and legacy settings', () => {
expect(extractDerivedDocumentEmailSettings().attachDocument).toBe(true);
expect(ZDocumentEmailSettingsSchema.parse(null).attachDocument).toBe(true);
expect(ZDocumentEmailSettingsInputSchema.parse({ documentCompleted: false })).toMatchObject({
attachDocument: true,
documentCompleted: false,
});
});
it('disables attachments independently of notification events', () => {
expect(ZDocumentEmailSettingsInputSchema.parse({ attachDocument: false })).toEqual({
...DEFAULT_DOCUMENT_EMAIL_SETTINGS,
attachDocument: false,
});
});
it.each(['false', 0, null, [], {}])('rejects invalid attachment values: %j', (attachDocument) => {
expect(ZDocumentEmailSettingsInputSchema.safeParse({ attachDocument }).success).toBe(false);
expect(ZDocumentMetaCreateSchema.safeParse({ emailSettings: { attachDocument } }).success).toBe(false);
expect(ZDocumentMetaUpdateSchema.safeParse({ emailSettings: { attachDocument } }).success).toBe(false);
});
it('does not replace an explicit opt-out with defaults when another input is invalid', () => {
expect(
ZDocumentMetaCreateSchema.safeParse({
emailSettings: { attachDocument: false, documentCompleted: 'true' },
}).success,
).toBe(false);
});
it('preserves the owner attachment setting when email distribution is disabled', () => {
const settings = extractDerivedDocumentEmailSettings({
distributionMethod: DocumentDistributionMethod.NONE,
emailSettings: { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: false },
} as DocumentMeta);
expect(settings).toMatchObject({
attachDocument: false,
documentCompleted: false,
ownerDocumentCompleted: true,
});
});
it('inherits organisation defaults, allows team overrides, and restores inheritance', () => {
const organisationSettings = generateDefaultOrganisationSettings();
organisationSettings.emailDocumentSettings = { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: false };
const teamSettings = generateDefaultTeamSettings();
const inherited = extractDerivedTeamSettings(organisationSettings, teamSettings);
expect(extractDerivedDocumentMeta(inherited, undefined).emailSettings.attachDocument).toBe(false);
teamSettings.emailDocumentSettings = { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: true };
const overridden = extractDerivedTeamSettings(organisationSettings, teamSettings);
expect(extractDerivedDocumentMeta(overridden, undefined).emailSettings.attachDocument).toBe(true);
teamSettings.emailDocumentSettings = null;
expect(
extractDerivedDocumentMeta(extractDerivedTeamSettings(organisationSettings, teamSettings), undefined)
.emailSettings.attachDocument,
).toBe(false);
});
it('allows a document or template to override inherited attachment defaults in either direction', () => {
const settings = generateDefaultOrganisationSettings();
expect(
extractDerivedDocumentMeta(settings, {
emailSettings: { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: false },
}).emailSettings.attachDocument,
).toBe(false);
settings.emailDocumentSettings = { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: false };
expect(
extractDerivedDocumentMeta(settings, {
emailSettings: { ...DEFAULT_DOCUMENT_EMAIL_SETTINGS, attachDocument: true },
}).emailSettings.attachDocument,
).toBe(true);
});
});
+16 -3
View File
@@ -14,8 +14,14 @@ export enum DocumentEmailEvents {
OwnerDocumentCreated = 'ownerDocumentCreated',
}
export const ZDocumentEmailSettingsSchema = z
export const ZDocumentEmailSettingsInputSchema = z
.object({
attachDocument: z
.boolean()
.describe(
'Whether to attach the completed document PDFs to emails sent to the owner and recipients. Document links are always included.',
)
.default(true),
recipientSigningRequest: z
.boolean()
.describe('Whether to send an email to all recipients that the document is ready for them to sign.')
@@ -55,8 +61,13 @@ export const ZDocumentEmailSettingsSchema = z
.describe('Whether to send an email to the document owner when a document is created from a direct template.')
.default(true),
})
.strip()
.catch(() => ({ ...DEFAULT_DOCUMENT_EMAIL_SETTINGS }));
.strip();
// Persisted settings may be absent or predate new options. API inputs must use
// the input schema so invalid values cannot silently enable document attachments.
export const ZDocumentEmailSettingsSchema = ZDocumentEmailSettingsInputSchema.catch(() => ({
...DEFAULT_DOCUMENT_EMAIL_SETTINGS,
}));
export type TDocumentEmailSettings = z.infer<typeof ZDocumentEmailSettingsSchema>;
@@ -68,6 +79,7 @@ export const extractDerivedDocumentEmailSettings = (documentMeta?: DocumentMeta
}
return {
attachDocument: emailSettings.attachDocument,
recipientSigningRequest: false,
recipientRemoved: false,
recipientSigned: false,
@@ -81,6 +93,7 @@ export const extractDerivedDocumentEmailSettings = (documentMeta?: DocumentMeta
};
export const DEFAULT_DOCUMENT_EMAIL_SETTINGS: TDocumentEmailSettings = {
attachDocument: true,
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: true,
+2 -2
View File
@@ -9,7 +9,7 @@ import { msg } from '@lingui/core/macro';
import { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
import { z } from 'zod';
import { ZDocumentEmailSettingsSchema } from './document-email';
import { ZDocumentEmailSettingsInputSchema } from './document-email';
/**
* The full document response schema.
@@ -125,7 +125,7 @@ export const ZDocumentMetaCreateSchema = z.object({
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.nullish(),
emailSettings: ZDocumentEmailSettingsInputSchema.nullish(),
envelopeExpirationPeriod: ZEnvelopeExpirationPeriod.nullish(),
reminderSettings: ZEnvelopeReminderSettings.nullish(),
});
@@ -1,5 +1,5 @@
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@@ -39,7 +39,7 @@ export const ZDistributeDocumentRequestSchema = z.object({
language: ZDocumentMetaLanguageSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.optional(),
});
@@ -1,4 +1,4 @@
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@@ -66,7 +66,7 @@ export const ZCreateEmbeddingDocumentRequestSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.optional(),
});
@@ -1,4 +1,4 @@
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@@ -62,7 +62,7 @@ export const ZCreateEmbeddingTemplateRequestSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.optional(),
});
@@ -1,4 +1,4 @@
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@@ -67,7 +67,7 @@ export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.optional(),
});
@@ -1,4 +1,4 @@
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@@ -66,7 +66,7 @@ export const ZUpdateEmbeddingTemplateRequestSchema = z.object({
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
})
.optional(),
});
@@ -1,5 +1,5 @@
import { ZEnvelopeExpirationPeriod } from '@documenso/lib/constants/envelope-expiration';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
import {
ZDocumentMetaDateFormatSchema,
@@ -89,7 +89,7 @@ export const ZUseEnvelopePayloadSchema = z.object({
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
@@ -4,7 +4,7 @@ import { ZEnvelopeReminderSettings } from '@documenso/lib/constants/envelope-rem
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { ZCssVarsSchema } from '@documenso/lib/types/css-vars';
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { ZSanitizeBrandingCssWarningSchema } from '@documenso/lib/utils/sanitize-branding-css';
@@ -41,7 +41,7 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
// emailReplyToName: z.string().optional(),
emailDocumentSettings: ZDocumentEmailSettingsSchema.optional(),
emailDocumentSettings: ZDocumentEmailSettingsInputSchema.optional(),
// AI features settings.
aiFeaturesEnabled: z.boolean().optional(),
@@ -4,7 +4,7 @@ import { ZEnvelopeReminderSettings } from '@documenso/lib/constants/envelope-rem
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { ZCssVarsSchema } from '@documenso/lib/types/css-vars';
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { ZSanitizeBrandingCssWarningSchema } from '@documenso/lib/utils/sanitize-branding-css';
@@ -44,7 +44,7 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
// emailReplyToName: z.string().nullish(),
emailDocumentSettings: ZDocumentEmailSettingsSchema.nullish(),
emailDocumentSettings: ZDocumentEmailSettingsInputSchema.nullish(),
// Default recipients settings.
defaultRecipients: ZDefaultRecipientsSchema.nullish(),
@@ -1,7 +1,7 @@
import { ZEnvelopeExpirationPeriod } from '@documenso/lib/constants/envelope-expiration';
import { ZDocumentSchema } from '@documenso/lib/types/document';
import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentEmailSettingsInputSchema } from '@documenso/lib/types/document-email';
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
import {
ZDocumentMetaDateFormatSchema,
@@ -60,7 +60,7 @@ export const ZTemplateMetaUpsertSchema = z.object({
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: zEmail().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
@@ -142,7 +142,7 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
emailSettings: ZDocumentEmailSettingsInputSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
@@ -21,6 +21,33 @@ export const DocumentEmailCheckboxes = ({ value, onChange, className, hiddenEven
return (
<div className={cn('space-y-3', className)}>
<div className="flex flex-row items-center">
<Checkbox
id="attachDocument"
className="h-5 w-5"
checked={value.attachDocument ?? true}
onCheckedChange={(checked) => onChange({ ...value, attachDocument: Boolean(checked) })}
/>
<label className="ml-2 flex flex-row items-center text-muted-foreground text-sm" htmlFor="attachDocument">
<Trans>Attach completed document PDFs to emails</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
<p>
<Trans>
Includes document PDFs in completion emails to the owner and all recipients, including CCs. Disable
this to send emails with document links only.
</Trans>
</p>
</TooltipContent>
</Tooltip>
</label>
</div>
{!isHidden(DocumentEmailEvents.RecipientSigned) && (
<div className="flex flex-row items-center">
<Checkbox