feat: add email domains (#1895)

Implemented Email Domains which allows Platform/Enterprise customers to
send emails to recipients using their custom emails.
This commit is contained in:
David Nguyen
2025-07-24 16:05:00 +10:00
committed by GitHub
parent 07119f0e8d
commit 3409aae411
157 changed files with 5966 additions and 1090 deletions

View File

@ -58,6 +58,9 @@ export const ZDocumentMetaDiffTypeSchema = z.enum([
'REDIRECT_URL',
'SUBJECT',
'TIMEZONE',
'EMAIL_ID',
'EMAIL_REPLY_TO',
'EMAIL_SETTINGS',
]);
export const ZFieldDiffTypeSchema = z.enum(['DIMENSION', 'POSITION']);
@ -109,6 +112,9 @@ export const ZDocumentAuditLogDocumentMetaSchema = z.union([
z.literal(DOCUMENT_META_DIFF_TYPE.REDIRECT_URL),
z.literal(DOCUMENT_META_DIFF_TYPE.SUBJECT),
z.literal(DOCUMENT_META_DIFF_TYPE.TIMEZONE),
z.literal(DOCUMENT_META_DIFF_TYPE.EMAIL_ID),
z.literal(DOCUMENT_META_DIFF_TYPE.EMAIL_REPLY_TO),
z.literal(DOCUMENT_META_DIFF_TYPE.EMAIL_SETTINGS),
]),
from: z.string().nullable(),
to: z.string().nullable(),

View File

@ -54,15 +54,7 @@ export const ZDocumentEmailSettingsSchema = z
.default(true),
})
.strip()
.catch(() => ({
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: true,
documentPending: true,
documentCompleted: true,
documentDeleted: true,
ownerDocumentCompleted: true,
}));
.catch(() => ({ ...DEFAULT_DOCUMENT_EMAIL_SETTINGS }));
export type TDocumentEmailSettings = z.infer<typeof ZDocumentEmailSettingsSchema>;
@ -88,3 +80,13 @@ export const extractDerivedDocumentEmailSettings = (
ownerDocumentCompleted: emailSettings.ownerDocumentCompleted,
};
};
export const DEFAULT_DOCUMENT_EMAIL_SETTINGS: TDocumentEmailSettings = {
recipientSigningRequest: true,
recipientRemoved: true,
recipientSigned: true,
documentPending: true,
documentCompleted: true,
documentDeleted: true,
ownerDocumentCompleted: true,
};

View File

@ -58,6 +58,8 @@ export const ZDocumentSchema = DocumentSchema.pick({
allowDictateNextSigner: true,
language: true,
emailSettings: true,
emailId: true,
emailReplyTo: true,
}).nullable(),
folder: FolderSchema.pick({
id: true,

View File

@ -0,0 +1,40 @@
import type { z } from 'zod';
import { EmailDomainSchema } from '@documenso/prisma/generated/zod/modelSchema/EmailDomainSchema';
import { ZOrganisationEmailLiteSchema } from './organisation-email';
/**
* The full email domain response schema.
*
* Mainly used for returning a single email domain from the API.
*/
export const ZEmailDomainSchema = EmailDomainSchema.pick({
id: true,
status: true,
organisationId: true,
domain: true,
selector: true,
publicKey: true,
createdAt: true,
updatedAt: true,
}).extend({
emails: ZOrganisationEmailLiteSchema.array(),
});
export type TEmailDomain = z.infer<typeof ZEmailDomainSchema>;
/**
* A version of the email domain response schema when returning multiple email domains at once from a single API endpoint.
*/
export const ZEmailDomainManySchema = EmailDomainSchema.pick({
id: true,
status: true,
organisationId: true,
domain: true,
selector: true,
createdAt: true,
updatedAt: true,
});
export type TEmailDomainMany = z.infer<typeof ZEmailDomainManySchema>;

View File

@ -0,0 +1,42 @@
import { EmailDomainStatus } from '@prisma/client';
import { z } from 'zod';
import { OrganisationEmailSchema } from '@documenso/prisma/generated/zod/modelSchema/OrganisationEmailSchema';
export const ZOrganisationEmailSchema = OrganisationEmailSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
email: true,
emailName: true,
// replyTo: true,
emailDomainId: true,
organisationId: true,
}).extend({
emailDomain: z.object({
id: z.string(),
status: z.nativeEnum(EmailDomainStatus),
}),
});
export type TOrganisationEmail = z.infer<typeof ZOrganisationEmailSchema>;
/**
* A lite version of the organisation email response schema without relations.
*/
export const ZOrganisationEmailLiteSchema = OrganisationEmailSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
email: true,
emailName: true,
// replyTo: true,
emailDomainId: true,
organisationId: true,
});
export const ZOrganisationEmailManySchema = ZOrganisationEmailLiteSchema.extend({
// Put anything extra here.
});
export type TOrganisationEmailMany = z.infer<typeof ZOrganisationEmailManySchema>;

View File

@ -19,6 +19,8 @@ export const ZClaimFlagsSchema = z.object({
unlimitedDocuments: z.boolean().optional(),
emailDomains: z.boolean().optional(),
embedAuthoring: z.boolean().optional(),
embedAuthoringWhiteLabel: z.boolean().optional(),
@ -50,6 +52,10 @@ export const SUBSCRIPTION_CLAIM_FEATURE_FLAGS: Record<
key: 'hidePoweredBy',
label: 'Hide Documenso branding by',
},
emailDomains: {
key: 'emailDomains',
label: 'Email domains',
},
embedAuthoring: {
key: 'embedAuthoring',
label: 'Embed authoring',
@ -128,6 +134,7 @@ export const internalClaims: InternalClaims = {
unlimitedDocuments: true,
allowCustomBranding: true,
hidePoweredBy: true,
emailDomains: true,
embedAuthoring: false,
embedAuthoringWhiteLabel: true,
embedSigning: false,
@ -144,6 +151,7 @@ export const internalClaims: InternalClaims = {
unlimitedDocuments: true,
allowCustomBranding: true,
hidePoweredBy: true,
emailDomains: true,
embedAuthoring: true,
embedAuthoringWhiteLabel: true,
embedSigning: true,

View File

@ -55,6 +55,8 @@ export const ZTemplateSchema = TemplateSchema.pick({
redirectUrl: true,
language: true,
emailSettings: true,
emailId: true,
emailReplyTo: true,
}).nullable(),
directLink: TemplateDirectLinkSchema.nullable(),
user: UserSchema.pick({