| id |
schema-web-standards-email |
| title |
Email Address Validation |
| category |
web-standards-validation |
| skillLevel |
beginner |
| tags |
schema |
validation |
web-standards |
email |
branded-types |
user-input |
|
| lessonOrder |
18 |
| rule |
| description |
Email Address Validation using Schema. |
|
| summary |
You're accepting email addresses from user input or API requests. A simple `string` type tells you nothing—users submit "not-an-email", "@@invalid", or empty strings. You need validation that checks... |
You're accepting email addresses from user input or API requests. A simple string type tells you nothing—users submit "not-an-email", "@@invalid", or empty strings. You need validation that checks format at runtime, creates a distinct type so emails can't be confused with other strings, and provides clear error messages.
import { Schema, Effect } from "effect"
// 1. Define branded Email type with validation
const Email = Schema.String.pipe(
Schema.trimmed(),
Schema.minLength(5),
Schema.maxLength(254),
Schema.pattern(
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
).pipe(
Schema.annotations({
description: "Valid email format (local@domain.tld)",
})
),
Schema.brand("Email")
)
type Email = typeof Email.Type
// 2. Create parser
const parseEmail = Schema.decodeUnknown(Email)
// 3. Use in request schemas
const CreateUserRequest = Schema.Struct({
name: Schema.String,
email: Email,
password: Schema.String.pipe(Schema.minLength(8)),
})
type CreateUserRequest = typeof CreateUserRequest.Type
// 4. Validate and use
const createUser = (input: unknown) =>
Effect.gen(function* () {
const request = yield* Schema.decodeUnknown(
CreateUserRequest
)(input).pipe(
Effect.mapError((error) => ({
_tag: "ValidationError" as const,
message: `Invalid request: ${error.message}`,
}))
)
// TypeScript knows request.email is Email, not string
const confirmEmail = (email: Email) => {
console.log(`Sending confirmation to ${email}`)
}
yield* Effect.sync(() => confirmEmail(request.email))
return { success: true, email: request.email }
})
// Usage
const userInput = {
name: "Alice",
email: "alice@example.com",
password: "securepass123",
}
Effect.runPromise(createUser(userInput))
.then((result) => console.log(result))
.catch((error) =>
console.error(`Error: ${error.message}`)
)
| Concept |
Explanation |
Schema.pattern |
Regex validation at runtime—catches invalid format |
Schema.trimmed() |
Remove leading/trailing whitespace |
Schema.maxLength(254) |
Email RFC 5321 limit |
Schema.brand("Email") |
Creates nominal type—Email ≠ string |
| Compile-time safety |
Functions requiring Email reject raw strings at compile time |
| Composable |
Reuse Email schema in any struct |
- User registration forms
- Contact/email collection forms
- API request validation
- Database operations where email is PK
- Anywhere email is accepted and stored