diff --git a/apps/dashboard/src/components/dashboard-account-settings/email-and-auth/emails-section.tsx b/apps/dashboard/src/components/dashboard-account-settings/email-and-auth/emails-section.tsx
new file mode 100644
index 000000000..f8edbf9a4
--- /dev/null
+++ b/apps/dashboard/src/components/dashboard-account-settings/email-and-auth/emails-section.tsx
@@ -0,0 +1,277 @@
+'use client';
+
+import { yupResolver } from "@hookform/resolvers/yup";
+import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors";
+import { strictEmailSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
+import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { DotsThree, EnvelopeSimple, Warning } from "@phosphor-icons/react";
+import { useEffect, useState } from "react";
+import { useForm } from "react-hook-form";
+import * as yup from "yup";
+import { useUser } from "@stackframe/stack";
+
+export function EmailsSection(props?: {
+ mockMode?: boolean,
+}) {
+ const isInMockMode = !!props?.mockMode;
+ const user = useUser({ or: isInMockMode ? 'return-null' : 'redirect' });
+
+ // In mock mode, show a placeholder message
+ if (isInMockMode && !user) {
+ return (
+
+
Emails
+ Email management is not available in demo mode.
+
+ );
+ }
+
+ if (!user) {
+ return null;
+ }
+
+ return ;
+}
+
+function EmailsSectionInner({ user }: { user: any }) {
+ const contactChannels = user.useContactChannels();
+ const [addingEmail, setAddingEmail] = useState(contactChannels.length === 0);
+ const [addingEmailLoading, setAddingEmailLoading] = useState(false);
+ const [addedEmail, setAddedEmail] = useState(null);
+ const isLastEmailUsedForAuth = contactChannels.filter((x: any) => x.usedForAuth && (x.type as string) === 'email').length === 1;
+
+ useEffect(() => {
+ if (addedEmail) {
+ runAsynchronously(async () => {
+ const cc = contactChannels.find((x: any) => x.value === addedEmail);
+ if (cc && !cc.isVerified) {
+ await cc.sendVerificationEmail();
+ }
+ setAddedEmail(null);
+ });
+ }
+ }, [contactChannels, addedEmail]);
+
+ const emailSchema = yupObject({
+ email: strictEmailSchema('Please enter a valid email address')
+ .notOneOf(contactChannels.map((x: any) => x.value), 'Email already exists')
+ .defined()
+ .nonEmpty('Email is required'),
+ });
+
+ const { register, handleSubmit, formState: { errors }, reset } = useForm({
+ resolver: yupResolver(emailSchema)
+ });
+
+ const onSubmit = async (data: yup.InferType) => {
+ setAddingEmailLoading(true);
+ try {
+ await user.createContactChannel({ type: 'email', value: data.email, usedForAuth: false });
+ setAddedEmail(data.email);
+ } finally {
+ setAddingEmailLoading(false);
+ }
+ setAddingEmail(false);
+ reset();
+ };
+
+ const sortedEmails = [...contactChannels]
+ .filter((x: any) => (x.type as string) === 'email')
+ .sort((a: any, b: any) => {
+ if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
+ if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
+ return 0;
+ });
+
+ return (
+
+
+
+
+ Email Addresses
+
+
+ Manage your personal email addresses, primary contact, and sign-in credentials.
+
+
+ {!addingEmail && (
+
+ )}
+
+
+ {addingEmail && (
+
+ )}
+
+ {sortedEmails.length > 0 && (
+
+ {sortedEmails.map((cc: any) => (
+
+
+
+
+
+
+
{cc.value}
+
+ {cc.isPrimary && (
+
+ Primary
+
+ )}
+ {!cc.isVerified && (
+
+ Unverified
+
+ )}
+ {cc.usedForAuth && (
+
+ Used for sign-in
+
+ )}
+
+
+
+
+
+
+
+
+
+ {!cc.isVerified && (
+ { await cc.sendVerificationEmail(); }}
+ className="cursor-pointer rounded-lg text-foreground focus:bg-zinc-50 dark:focus:bg-zinc-900"
+ >
+ Verify Email
+
+ )}
+ {!cc.isPrimary && cc.isVerified && (
+ { await cc.update({ isPrimary: true }); }}
+ className="cursor-pointer rounded-lg text-foreground focus:bg-zinc-50 dark:focus:bg-zinc-900"
+ >
+ Set as Primary
+
+ )}
+ {!cc.isPrimary && !cc.isVerified && (
+
+ Set as Primary (Verify first)
+
+ )}
+ {!cc.usedForAuth && cc.isVerified && (
+ {
+ try {
+ await cc.update({ usedForAuth: true });
+ } catch (e) {
+ if (KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse.isInstance(e)) {
+ alert("This email is already used for sign-in by another user.");
+ }
+ }
+ }}
+ className="cursor-pointer rounded-lg text-foreground focus:bg-zinc-50 dark:focus:bg-zinc-900"
+ >
+ Enable Sign-in
+
+ )}
+ {cc.usedForAuth && !isLastEmailUsedForAuth && (
+ { await cc.update({ usedForAuth: false }); }}
+ className="cursor-pointer rounded-lg text-foreground focus:bg-zinc-50 dark:focus:bg-zinc-900"
+ >
+ Disable Sign-in
+
+ )}
+ {cc.usedForAuth && isLastEmailUsedForAuth && (
+
+ Disable Sign-in (Last auth email)
+
+ )}
+
+ {(!isLastEmailUsedForAuth || !cc.usedForAuth) ? (
+ { await cc.delete(); }}
+ className="cursor-pointer rounded-lg text-red-500 hover:text-red-600 focus:text-red-500"
+ >
+ Remove Email
+
+ ) : (
+
+ Remove Email (Last auth email)
+
+ )}
+
+
+
+ ))}
+
+ )}
+
+ );
+}