diff --git a/.env.example b/.env.example
index 3dd78566a2..65abc19de8 100644
--- a/.env.example
+++ b/.env.example
@@ -65,6 +65,11 @@ CAP_AWS_ENDPOINT=${NEXT_PUBLIC_CAP_AWS_ENDPOINT}
 # Necessary for authentication, genearte by running `openssl rand -base64 32`
 NEXTAUTH_SECRET=
 
+# Restrict signup to specific email domains (comma-separated)
+# If empty or not set, signup is open to all email addresses
+# Example: CAP_ALLOWED_SIGNUP_DOMAINS=company.com,partner.org
+# CAP_ALLOWED_SIGNUP_DOMAINS=
+
 # Provide if you want to use Google authentication
 # GOOGLE_CLIENT_ID=
 # GOOGLE_CLIENT_SECRET=
diff --git a/packages/database/auth/auth-options.tsx b/packages/database/auth/auth-options.tsx
index 99b0c04974..2d7afe8ed5 100644
--- a/packages/database/auth/auth-options.tsx
+++ b/packages/database/auth/auth-options.tsx
@@ -14,6 +14,7 @@ import { sendEmail } from "../emails/config";
 import { LoginLink } from "../emails/login-link";
 import { nanoId } from "../helpers";
 import { organizationMembers, organizations, users } from "../schema";
+import { isEmailAllowedForSignup } from "./domain-utils";
 import { DrizzleAdapter } from "./drizzle-adapter";
 
 export const config = {
@@ -174,6 +175,29 @@ export const authOptions = (): NextAuthOptions => {
 			},
 		},
 		callbacks: {
+			async signIn({ user }) {
+				const allowedDomains = serverEnv().CAP_ALLOWED_SIGNUP_DOMAINS;
+				if (!allowedDomains) return true;
+
+				if (user.email) {
+					const [existingUser] = await db()
+						.select()
+						.from(users)
+						.where(eq(users.email, user.email))
+						.limit(1);
+
+					// Only apply domain restrictions for new users, existing ones can always sign in
+					if (
+						!existingUser &&
+						!isEmailAllowedForSignup(user.email, allowedDomains)
+					) {
+						console.warn(`Signup blocked for email domain: ${user.email}`);
+						return false;
+					}
+				}
+
+				return true;
+			},
 			async session({ token, session }) {
 				if (!session.user) return session;
 
diff --git a/packages/database/auth/domain-utils.ts b/packages/database/auth/domain-utils.ts
new file mode 100644
index 0000000000..9f6438301a
--- /dev/null
+++ b/packages/database/auth/domain-utils.ts
@@ -0,0 +1,50 @@
+import { z } from "zod";
+
+export function isEmailAllowedForSignup(
+	email: string,
+	allowedDomainsConfig?: string,
+): boolean {
+	// If no domain restrictions are configured, allow all signups
+	if (!allowedDomainsConfig || allowedDomainsConfig.trim() === "") {
+		return true;
+	}
+
+	const emailDomain = extractDomainFromEmail(email);
+	if (!emailDomain) {
+		return false;
+	}
+
+	const allowedDomains = parseAllowedDomains(allowedDomainsConfig);
+	return allowedDomains.includes(emailDomain.toLowerCase());
+}
+
+function extractDomainFromEmail(email: string): string | null {
+	// TODO: replace with zod v4's z.email()
+	const emailValidation = z.string().email().safeParse(email);
+	if (!emailValidation.success) {
+		return null;
+	}
+
+	// Extract domain from validated email
+	const atIndex = email.lastIndexOf("@");
+	return atIndex !== -1 ? email.substring(atIndex + 1) : null;
+}
+
+function parseAllowedDomains(allowedDomainsConfig: string): string[] {
+	return allowedDomainsConfig
+		.split(",")
+		.map((domain) => domain.trim().toLowerCase())
+		.filter((domain) => domain.length > 0 && isValidDomain(domain));
+}
+
+function isValidDomain(domain: string): boolean {
+	// TODO: replace this polyfill with zod v4's z.hostname()
+	const hostnameRegex =
+		/^(?=.{1,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$|localhost)$/;
+	return z
+		.string()
+		.refine((val) => hostnameRegex.test(val), {
+			message: "Invalid hostname",
+		})
+		.safeParse(domain).success;
+}
diff --git a/packages/env/server.ts b/packages/env/server.ts
index 0868f43626..678825bc51 100644
--- a/packages/env/server.ts
+++ b/packages/env/server.ts
@@ -45,6 +45,7 @@ function createServerEnv() {
 			GROQ_API_KEY: z.string().optional(),
 			INTERCOM_SECRET: z.string().optional(),
 			CAP_VIDEOS_DEFAULT_PUBLIC: boolString(true),
+			CAP_ALLOWED_SIGNUP_DOMAINS: z.string().optional(),
 			VERCEL_ENV: z
 				.union([
 					z.literal("production"),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 880d44daf2..d886fd36b0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12567,11 +12567,12 @@ packages:
   superagent@8.1.2:
     resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==}
     engines: {node: '>=6.4.0 <13 || >=14'}
-    deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net
+    deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net
 
   supertest@6.3.4:
     resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==}
     engines: {node: '>=6.4.0'}
+    deprecated: Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net
 
   supports-color@10.0.0:
     resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}
