diff --git a/frontend/src/lib/components/ui/email-input/EmailInput.svelte b/frontend/src/lib/components/ui/email-input/EmailInput.svelte
new file mode 100644
index 0000000..d82f907
--- /dev/null
+++ b/frontend/src/lib/components/ui/email-input/EmailInput.svelte
@@ -0,0 +1,91 @@
+
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
diff --git a/frontend/src/lib/components/ui/email-input/index.ts b/frontend/src/lib/components/ui/email-input/index.ts
new file mode 100644
index 0000000..b787b1a
--- /dev/null
+++ b/frontend/src/lib/components/ui/email-input/index.ts
@@ -0,0 +1,3 @@
+import EmailInput from './EmailInput.svelte';
+
+export { EmailInput };
diff --git a/frontend/src/lib/utils/email.ts b/frontend/src/lib/utils/email.ts
new file mode 100644
index 0000000..22620cb
--- /dev/null
+++ b/frontend/src/lib/utils/email.ts
@@ -0,0 +1,27 @@
+/**
+ * Normalizes an email: trims whitespace and lowercases.
+ */
+export function normalizeEmail(value: string): string {
+ return value.trim().toLowerCase();
+}
+
+/**
+ * Validates an email address.
+ * Checks: non-empty, max 254 chars, passes a basic RFC-aware regex
+ * and a mailto: URI test via HTMLInputElement.
+ */
+export function isValidEmail(email: string): boolean {
+ const trimmed = email.trim();
+ if (!trimmed) return false;
+ if (trimmed.length > 254) return false;
+
+ // Basic structural regex — no RFC 5322 full validation, but catches obvious junk
+ if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(trimmed)) return false;
+
+ // Use the browser's built-in email validation via type=email
+ // (catches things like multiple @ signs)
+ const input = document.createElement('input');
+ input.type = 'email';
+ input.value = trimmed;
+ return input.checkValidity();
+}