diff --git a/apps/web/app/(org)/login/form.tsx b/apps/web/app/(org)/login/form.tsx
index f174300b0a..f29c4a60ab 100644
--- a/apps/web/app/(org)/login/form.tsx
+++ b/apps/web/app/(org)/login/form.tsx
@@ -8,13 +8,12 @@ import {
 	faExclamationCircle,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useMutation } from "@tanstack/react-query";
 import { AnimatePresence, motion } from "framer-motion";
 import Cookies from "js-cookie";
 import { LucideArrowUpRight } from "lucide-react";
 import Image from "next/image";
 import Link from "next/link";
-import { useSearchParams } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";
 import { signIn } from "next-auth/react";
 import { Suspense, useEffect, useState } from "react";
 import { toast } from "sonner";
@@ -28,46 +27,20 @@ const MotionButton = motion(Button);
 
 export function LoginForm() {
 	const searchParams = useSearchParams();
+	const router = useRouter();
 	const next = searchParams?.get("next");
 	const [email, setEmail] = useState("");
+	const [loading, setLoading] = useState(false);
+	const [emailSent, setEmailSent] = useState(false);
 	const [oauthError, setOauthError] = useState(false);
 	const [showOrgInput, setShowOrgInput] = useState(false);
 	const [organizationId, setOrganizationId] = useState("");
 	const [organizationName, setOrganizationName] = useState<string | null>(null);
+	const [lastEmailSentTime, setLastEmailSentTime] = useState<number | null>(
+		null,
+	);
 	const theme = Cookies.get("theme") || "light";
 
-	const emailSignInMutation = useMutation({
-		mutationFn: async (email: string) => {
-			trackEvent("auth_started", {
-				method: "email",
-				is_signup: !oauthError,
-			});
-
-			const result = await signIn("email", {
-				email,
-				redirect: false,
-				...(next && next.length > 0 ? { callbackUrl: next } : {}),
-			});
-
-			if (!result?.ok || result?.error) {
-				throw new Error("Failed to send email");
-			}
-
-			return result;
-		},
-		onSuccess: () => {
-			trackEvent("auth_email_sent", {
-				email_domain: email.split("@")[1],
-			});
-			toast.success("Email sent - check your inbox!");
-		},
-		onError: () => {
-			toast.error("Error sending email - try again?");
-		},
-	});
-
-	const emailSent = emailSignInMutation.isSuccess;
-
 	useEffect(() => {
 		theme === "dark"
 			? (document.body.className = "dark")
@@ -123,8 +96,6 @@ export function LoginForm() {
 				});
 				const data = await response.json();
 
-				console.log(data);
-
 				if (data.url) {
 					window.location.href = data.url;
 				}
@@ -274,7 +245,64 @@ export function LoginForm() {
 											e.preventDefault();
 											if (!email) return;
 
-											emailSignInMutation.mutate(email);
+											// Check if we're rate limited on the client side
+											if (lastEmailSentTime) {
+												const timeSinceLastRequest =
+													Date.now() - lastEmailSentTime;
+												const waitTime = 30000; // 30 seconds
+												if (timeSinceLastRequest < waitTime) {
+													const remainingSeconds = Math.ceil(
+														(waitTime - timeSinceLastRequest) / 1000,
+													);
+													toast.error(
+														`Please wait ${remainingSeconds} seconds before requesting a new code`,
+													);
+													return;
+												}
+											}
+
+											setLoading(true);
+											trackEvent("auth_started", {
+												method: "email",
+												is_signup: !oauthError,
+											});
+											signIn("email", {
+												email,
+												redirect: false,
+												...(next && next.length > 0
+													? { callbackUrl: next }
+													: {}),
+											})
+												.then((res) => {
+													setLoading(false);
+
+													if (res?.ok && !res?.error) {
+														setEmailSent(true);
+														setLastEmailSentTime(Date.now());
+														trackEvent("auth_email_sent", {
+															email_domain: email.split("@")[1],
+														});
+														const params = new URLSearchParams({
+															email,
+															...(next && { next }),
+															lastSent: Date.now().toString(),
+														});
+														router.push(`/verify-otp?${params.toString()}`);
+													} else {
+														// NextAuth always returns "EmailSignin" for all email provider errors
+														// Since we already check rate limiting on the client side before sending,
+														// if we get an error here, it's likely rate limiting from the server
+														toast.error(
+															"Please wait 30 seconds before requesting a new code",
+														);
+													}
+												})
+												.catch((error) => {
+													setEmailSent(false);
+													setLoading(false);
+													// Catch block is rarely triggered with NextAuth
+													toast.error("Error sending email - try again?");
+												});
 										}}
 										className="flex flex-col space-y-3"
 									>
@@ -283,7 +311,7 @@ export function LoginForm() {
 											email={email}
 											emailSent={emailSent}
 											setEmail={setEmail}
-											loading={emailSignInMutation.isPending}
+											loading={loading}
 											oauthError={oauthError}
 											handleGoogleSignIn={handleGoogleSignIn}
 										/>
@@ -320,8 +348,9 @@ export function LoginForm() {
 							layout
 							className="pt-3 mx-auto text-sm underline text-gray-10 hover:text-gray-8"
 							onClick={() => {
+								setEmailSent(false);
 								setEmail("");
-								emailSignInMutation.reset();
+								setLoading(false);
 							}}
 						>
 							Click to restart sign in process
diff --git a/apps/web/app/(org)/verify-otp/form.tsx b/apps/web/app/(org)/verify-otp/form.tsx
new file mode 100644
index 0000000000..8ed33dcdfe
--- /dev/null
+++ b/apps/web/app/(org)/verify-otp/form.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import { Button, LogoBadge } from "@cap/ui";
+import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useMutation } from "@tanstack/react-query";
+import { motion } from "framer-motion";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { signIn } from "next-auth/react";
+import { useEffect, useRef, useState } from "react";
+import { toast } from "sonner";
+
+export function VerifyOTPForm({
+	email,
+	next,
+	lastSent,
+}: {
+	email: string;
+	next?: string;
+	lastSent?: string;
+}) {
+	const [code, setCode] = useState(["", "", "", "", "", ""]);
+	const [lastResendTime, setLastResendTime] = useState<number | null>(
+		lastSent ? parseInt(lastSent) : null,
+	);
+	const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
+	const router = useRouter();
+
+	useEffect(() => {
+		inputRefs.current[0]?.focus();
+	}, []);
+
+	const handleChange = (index: number, value: string) => {
+		if (value.length > 1) {
+			const pastedCode = value.slice(0, 6).split("");
+			const newCode = [...code];
+			pastedCode.forEach((digit, i) => {
+				if (index + i < 6) {
+					newCode[index + i] = digit;
+				}
+			});
+			setCode(newCode);
+
+			const nextEmptyIndex = newCode.findIndex((digit) => digit === "");
+			if (nextEmptyIndex !== -1) {
+				inputRefs.current[nextEmptyIndex]?.focus();
+			} else {
+				inputRefs.current[5]?.focus();
+			}
+
+			if (index + value.length >= 5) handleVerify.mutate();
+		} else {
+			const newCode = [...code];
+			newCode[index] = value;
+			setCode(newCode);
+
+			if (value && index < 5) {
+				inputRefs.current[index + 1]?.focus();
+			}
+		}
+	};
+
+	const handleKeyDown = (
+		index: number,
+		e: React.KeyboardEvent<HTMLInputElement>,
+	) => {
+		if (e.key === "Backspace" && !code[index] && index > 0) {
+			inputRefs.current[index - 1]?.focus();
+		}
+	};
+
+	const handleVerify = useMutation({
+		mutationFn: async () => {
+			const otpCode = code.join("");
+			if (otpCode.length !== 6) throw "Please enter a complete 6-digit code";
+
+			// shoutout https://github.com/buoyad/Tally/pull/14
+			const res = await fetch(
+				`/api/auth/callback/email?email=${encodeURIComponent(email)}&token=${encodeURIComponent(otpCode)}&callbackUrl=${encodeURIComponent("/login-success")}`,
+			);
+
+			if (!res.url.includes("/login-success")) {
+				setCode(["", "", "", "", "", ""]);
+				inputRefs.current[0]?.focus();
+				throw "Invalid code. Please try again.";
+			}
+		},
+		onSuccess: () => {
+			router.replace(next || "/dashboard");
+		},
+		onError: (e) => {
+			if (typeof e === "string") {
+				toast.error(e);
+			} else {
+				toast.error("An error occurred. Please try again.");
+			}
+		},
+	});
+
+	const handleResend = useMutation({
+		mutationFn: async () => {
+			// Check client-side rate limiting
+			if (lastResendTime) {
+				const timeSinceLastRequest = Date.now() - lastResendTime;
+				const waitTime = 30000; // 30 seconds
+				if (timeSinceLastRequest < waitTime) {
+					const remainingSeconds = Math.ceil(
+						(waitTime - timeSinceLastRequest) / 1000,
+					);
+
+					throw `Please wait ${remainingSeconds} seconds before requesting a new code`;
+				}
+			}
+
+			const result = await signIn("email", {
+				email,
+				redirect: false,
+			});
+
+			if (result?.error) {
+				// NextAuth returns generic "EmailSignin" error for all email errors
+				throw "Please wait 30 seconds before requesting a new code";
+			}
+		},
+		onSuccess: () => {
+			toast.success("A new code has been sent to your email!");
+			setCode(["", "", "", "", "", ""]);
+			inputRefs.current[0]?.focus();
+			setLastResendTime(Date.now());
+		},
+		onError: (e) => {
+			if (typeof e === "string") {
+				toast.error(e);
+			} else {
+				toast.error("An error occurred. Please try again.");
+			}
+		},
+	});
+
+	const isVerifying = handleVerify.isPending || handleVerify.isSuccess;
+
+	return (
+		<motion.div
+			initial={{ opacity: 0, y: 20 }}
+			animate={{ opacity: 1, y: 0 }}
+			className="relative w-[calc(100%-5%)] p-[28px] max-w-[432px] bg-gray-3 border border-gray-5 rounded-2xl"
+		>
+			<Link
+				href="/login"
+				className="absolute top-5 left-5 z-20 flex gap-2 items-center py-1.5 px-3 text-gray-12 bg-transparent border border-gray-4 rounded-full hover:bg-gray-1 transition-colors duration-300"
+			>
+				<FontAwesomeIcon className="w-2" icon={faArrowLeft} />
+				<p className="text-xs">Back</p>
+			</Link>
+
+			<Link className="flex mx-auto size-fit" href="/">
+				<LogoBadge className="w-[72px] h-[72px]" />
+			</Link>
+
+			<div className="flex flex-col justify-center items-center my-7 text-center">
+				<h1 className="text-2xl font-semibold text-gray-12">
+					Enter verification code
+				</h1>
+				<p className="text-[16px] text-gray-10 mt-2">
+					We sent a 6-digit code to {email}
+				</p>
+			</div>
+
+			<div className="flex justify-center gap-2 mb-6">
+				{code.map((digit, index) => (
+					<input
+						key={index}
+						ref={(el) => (inputRefs.current[index] = el)}
+						type="text"
+						inputMode="numeric"
+						pattern="[0-9]*"
+						maxLength={1}
+						value={digit}
+						onChange={(e) =>
+							handleChange(index, e.target.value.replace(/\D/g, ""))
+						}
+						onKeyDown={(e) => handleKeyDown(index, e)}
+						onPaste={(e) => {
+							e.preventDefault();
+							const pastedData = e.clipboardData
+								.getData("text")
+								.replace(/\D/g, "");
+							handleChange(0, pastedData);
+						}}
+						className="w-12 h-14 text-center text-xl font-semibold bg-gray-1 border border-gray-5 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
+						disabled={handleVerify.isPending || handleVerify.isSuccess}
+					/>
+				))}
+			</div>
+
+			<Button
+				variant="primary"
+				className="w-full"
+				onClick={() => handleVerify.mutate()}
+				disabled={code.some((digit) => !digit) || isVerifying}
+			>
+				{isVerifying ? "Verifying..." : "Verify Code"}
+			</Button>
+
+			<div className="mt-4 text-center">
+				<button
+					type="button"
+					onClick={() => handleResend.mutate()}
+					disabled={handleResend.isPending}
+					className="text-sm text-gray-10 hover:text-gray-12 underline transition-colors"
+				>
+					{handleResend.isPending
+						? "Sending..."
+						: "Didn't receive the code? Resend"}
+				</button>
+			</div>
+
+			<p className="mt-6 text-xs text-center text-gray-9">
+				By verifying your email, you acknowledge that you have both read and
+				agree to Cap's{" "}
+				<Link
+					href="/terms"
+					target="_blank"
+					className="text-xs font-semibold text-gray-12 hover:text-blue-300"
+				>
+					Terms of Service
+				</Link>{" "}
+				and{" "}
+				<Link
+					href="/privacy"
+					target="_blank"
+					className="text-xs font-semibold text-gray-12 hover:text-blue-300"
+				>
+					Privacy Policy
+				</Link>
+				.
+			</p>
+		</motion.div>
+	);
+}
diff --git a/apps/web/app/(org)/verify-otp/page.tsx b/apps/web/app/(org)/verify-otp/page.tsx
new file mode 100644
index 0000000000..7ef664dc63
--- /dev/null
+++ b/apps/web/app/(org)/verify-otp/page.tsx
@@ -0,0 +1,36 @@
+import { getSession } from "@cap/database/auth/session";
+import { redirect } from "next/navigation";
+import { Suspense } from "react";
+import { VerifyOTPForm } from "./form";
+
+export const metadata = {
+	title: "Verify Code | Cap",
+};
+
+export default async function VerifyOTPPage({
+	searchParams,
+}: {
+	searchParams: { email?: string; next?: string; lastSent?: string };
+}) {
+	const session = await getSession();
+
+	if (session?.user) {
+		redirect(searchParams.next || "/dashboard");
+	}
+
+	if (!searchParams.email) {
+		redirect("/login");
+	}
+
+	return (
+		<div className="flex h-screen w-full items-center justify-center">
+			<Suspense fallback={null}>
+				<VerifyOTPForm
+					email={searchParams.email}
+					next={searchParams.next}
+					lastSent={searchParams.lastSent}
+				/>
+			</Suspense>
+		</div>
+	);
+}
diff --git a/packages/database/auth/auth-options.tsx b/packages/database/auth/auth-options.tsx
index 2d7afe8ed5..85e0961b2a 100644
--- a/packages/database/auth/auth-options.tsx
+++ b/packages/database/auth/auth-options.tsx
@@ -1,4 +1,5 @@
 import { serverEnv } from "@cap/env";
+import crypto from "crypto";
 import { eq } from "drizzle-orm";
 import { cookies } from "next/headers";
 import type { NextAuthOptions } from "next-auth";
@@ -11,10 +12,8 @@ import WorkOSProvider from "next-auth/providers/workos";
 import { db } from "../";
 import { dub } from "../dub";
 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 = {
@@ -72,17 +71,36 @@ export const authOptions = (): NextAuthOptions => {
 					},
 				}),
 				EmailProvider({
-					async sendVerificationRequest({ identifier, url }) {
+					async generateVerificationToken() {
+						return crypto.randomInt(100000, 1000000).toString();
+					},
+					async sendVerificationRequest({ identifier, token }) {
 						console.log("sendVerificationRequest");
+
 						if (!serverEnv().RESEND_API_KEY) {
-							console.log(`Login link: ${url}`);
+							console.log("\n");
+							console.log(
+								"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
+							);
+							console.log("🔐 VERIFICATION CODE (Development Mode)");
+							console.log(
+								"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
+							);
+							console.log(`📧 Email: ${identifier}`);
+							console.log(`🔢 Code: ${token}`);
+							console.log(`⏱️  Expires in: 10 minutes`);
+							console.log(
+								"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
+							);
+							console.log("\n");
 						} else {
-							console.log({ identifier, url });
-							const email = LoginLink({ url, email: identifier });
+							console.log({ identifier, token });
+							const { OTPEmail } = await import("../emails/otp-email");
+							const email = OTPEmail({ code: token, email: identifier });
 							console.log({ email });
 							await sendEmail({
 								email: identifier,
-								subject: `Your Cap Login Link`,
+								subject: `Your Cap Verification Code`,
 								react: email,
 							});
 						}
@@ -105,7 +123,6 @@ export const authOptions = (): NextAuthOptions => {
 		},
 		events: {
 			async signIn({ user, account, isNewUser }) {
-				// Check if user needs organization setup (new user or guest checkout user)
 				const [dbUser] = await db()
 					.select()
 					.from(users)
@@ -135,10 +152,7 @@ export const authOptions = (): NextAuthOptions => {
 
 							console.log("Dub tracking successful:", trackResult);
 
-							// Properly delete the dub_id cookie
 							cookies().delete("dub_id");
-
-							// Also delete dub_partner_data if it exists
 							if (dubPartnerData) {
 								cookies().delete("dub_partner_data");
 							}
@@ -175,29 +189,6 @@ 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/drizzle-adapter.ts b/packages/database/auth/drizzle-adapter.ts
index 7e15b2dde7..3d1b6902dc 100644
--- a/packages/database/auth/drizzle-adapter.ts
+++ b/packages/database/auth/drizzle-adapter.ts
@@ -182,7 +182,6 @@ export function DrizzleAdapter(db: PlanetScaleDatabase): Adapter {
 			await db.delete(sessions).where(eq(sessions.sessionToken, sessionToken));
 		},
 		async createVerificationToken(verificationToken) {
-			console.log({ verificationToken });
 			// First, check if a token for the given identifier already exists
 			const existingTokens = await db
 				.select()
diff --git a/packages/database/emails/otp-email.tsx b/packages/database/emails/otp-email.tsx
new file mode 100644
index 0000000000..0d61262266
--- /dev/null
+++ b/packages/database/emails/otp-email.tsx
@@ -0,0 +1,66 @@
+import { CAP_LOGO_URL } from "@cap/utils";
+import {
+	Body,
+	Container,
+	Head,
+	Heading,
+	Html,
+	Img,
+	Preview,
+	Section,
+	Tailwind,
+	Text,
+} from "@react-email/components";
+import Footer from "./components/Footer";
+
+export function OTPEmail({
+	email = "",
+	code = "",
+}: {
+	email: string;
+	code: string;
+}) {
+	return (
+		<Html>
+			<Head />
+			<Preview>Your Cap Verification Code: {code}</Preview>
+			<Tailwind>
+				<Body className="mx-auto my-auto bg-gray-1 font-sans">
+					<Container className="mx-auto my-10 max-w-[500px] rounded border border-solid border-gray-200 px-10 py-5">
+						<Section className="mt-8">
+							<Img
+								src={CAP_LOGO_URL}
+								width="40"
+								height="40"
+								alt="Cap"
+								className="mx-auto my-0"
+							/>
+						</Section>
+						<Heading className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
+							Your Verification Code
+						</Heading>
+						<Text className="text-sm leading-6 text-black">
+							Welcome to Cap!
+						</Text>
+						<Text className="text-sm leading-6 text-black">
+							Please use the following verification code to sign in to your
+							account:
+						</Text>
+						<Section className="my-8 text-center">
+							<div className="rounded-lg bg-gray-100 px-8 py-6">
+								<Text className="m-0 text-3xl font-bold tracking-wider text-black">
+									{code}
+								</Text>
+							</div>
+						</Section>
+						<Text className="text-sm leading-6 text-black">
+							This code will expire in 10 minutes. If you didn't request this
+							code, you can safely ignore this email.
+						</Text>
+						<Footer email={email} />
+					</Container>
+				</Body>
+			</Tailwind>
+		</Html>
+	);
+}
diff --git a/packages/database/migrations/meta/0007_snapshot.json b/packages/database/migrations/meta/0007_snapshot.json
index 1345655f72..6e45d78285 100644
--- a/packages/database/migrations/meta/0007_snapshot.json
+++ b/packages/database/migrations/meta/0007_snapshot.json
@@ -1,7 +1,7 @@
 {
 	"version": "5",
 	"dialect": "mysql",
-	"id": "ccef8ed8-2472-41a3-bf54-900db6ce8b08",
+	"id": "308bd25e-8d1d-4bc6-ba35-880bf4d18a6d",
 	"prevId": "140a5aa9-5b37-4435-baeb-5fd016ef5c6e",
 	"tables": {
 		"accounts": {
@@ -395,76 +395,6 @@
 			},
 			"checkConstraint": {}
 		},
-		"notifications": {
-			"name": "notifications",
-			"columns": {
-				"id": {
-					"name": "id",
-					"type": "varchar(15)",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false
-				},
-				"orgId": {
-					"name": "orgId",
-					"type": "varchar(15)",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false
-				},
-				"recipientId": {
-					"name": "recipientId",
-					"type": "varchar(15)",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false
-				},
-				"type": {
-					"name": "type",
-					"type": "varchar(10)",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false
-				},
-				"data": {
-					"name": "data",
-					"type": "json",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false
-				},
-				"readAt": {
-					"name": "readAt",
-					"type": "timestamp",
-					"primaryKey": false,
-					"notNull": false,
-					"autoincrement": false
-				},
-				"createdAt": {
-					"name": "createdAt",
-					"type": "timestamp",
-					"primaryKey": false,
-					"notNull": true,
-					"autoincrement": false,
-					"default": "(now())"
-				}
-			},
-			"indexes": {},
-			"foreignKeys": {},
-			"compositePrimaryKeys": {
-				"notifications_id": {
-					"name": "notifications_id",
-					"columns": ["id"]
-				}
-			},
-			"uniqueConstraints": {
-				"notifications_id_unique": {
-					"name": "notifications_id_unique",
-					"columns": ["id"]
-				}
-			},
-			"checkConstraint": {}
-		},
 		"organization_invites": {
 			"name": "organization_invites",
 			"columns": {
@@ -772,6 +702,95 @@
 			},
 			"checkConstraint": {}
 		},
+		"otp_codes": {
+			"name": "otp_codes",
+			"columns": {
+				"id": {
+					"name": "id",
+					"type": "varchar(15)",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false
+				},
+				"identifier": {
+					"name": "identifier",
+					"type": "varchar(255)",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false
+				},
+				"code": {
+					"name": "code",
+					"type": "varchar(6)",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false
+				},
+				"expires": {
+					"name": "expires",
+					"type": "datetime",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false
+				},
+				"attempts": {
+					"name": "attempts",
+					"type": "int",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false,
+					"default": 0
+				},
+				"createdAt": {
+					"name": "createdAt",
+					"type": "timestamp",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false,
+					"default": "(now())"
+				},
+				"updatedAt": {
+					"name": "updatedAt",
+					"type": "timestamp",
+					"primaryKey": false,
+					"notNull": true,
+					"autoincrement": false,
+					"onUpdate": true,
+					"default": "(now())"
+				}
+			},
+			"indexes": {
+				"identifier_idx": {
+					"name": "identifier_idx",
+					"columns": ["identifier"],
+					"isUnique": false
+				},
+				"code_idx": {
+					"name": "code_idx",
+					"columns": ["code"],
+					"isUnique": false
+				},
+				"expires_idx": {
+					"name": "expires_idx",
+					"columns": ["expires"],
+					"isUnique": false
+				}
+			},
+			"foreignKeys": {},
+			"compositePrimaryKeys": {
+				"otp_codes_id": {
+					"name": "otp_codes_id",
+					"columns": ["id"]
+				}
+			},
+			"uniqueConstraints": {
+				"otp_codes_id_unique": {
+					"name": "otp_codes_id_unique",
+					"columns": ["id"]
+				}
+			},
+			"checkConstraint": {}
+		},
 		"s3_buckets": {
 			"name": "s3_buckets",
 			"columns": {
@@ -1363,14 +1382,6 @@
 					"notNull": false,
 					"autoincrement": false
 				},
-				"preferences": {
-					"name": "preferences",
-					"type": "json",
-					"primaryKey": false,
-					"notNull": false,
-					"autoincrement": false,
-					"default": "('null')"
-				},
 				"activeOrganizationId": {
 					"name": "activeOrganizationId",
 					"type": "varchar(15)",
diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json
index 8f87fbe097..808fe48f79 100644
--- a/packages/database/migrations/meta/_journal.json
+++ b/packages/database/migrations/meta/_journal.json
@@ -54,15 +54,8 @@
 		{
 			"idx": 7,
 			"version": "5",
-			"when": 1754318170128,
-			"tag": "0007_first_lilith",
-			"breakpoints": true
-		},
-		{
-			"idx": 8,
-			"version": "5",
-			"when": 1754326114877,
-			"tag": "0008_illegal_rogue",
+			"when": 1754314124918,
+			"tag": "0007_cheerful_rocket_raccoon",
 			"breakpoints": true
 		}
 	]
