The Email module verifies a user's email address via OTP (One-Time Password).
Follows the form-based pattern (with the OTP sub-loop). See that page for the shared manager lifecycle and skeleton; the rest of this page covers Email-specific config, states, and methods.
Tag
<incode-email> is a standard Web Component. Importing the UI subpath registers the custom element; importing the CSS applies the module's styles.
import '@incodetech/web/email';
import '@incodetech/web/email/styles.css';
Properties
Set these as JavaScript properties on the element (not as HTML attributes):
| Property | Type | Required | Description |
|---|---|---|---|
config |
EmailConfig |
✅ | Configuration options |
onFinish |
() => void |
❌ | Called when verification completes |
onError |
(error: string) => void |
❌ | Called when an error occurs |
Usage
Vanilla HTML / TypeScript
<incode-email></incode-email>
<script type="module">
import { setup } from '@incodetech/core';
import '@incodetech/web/email';
import '@incodetech/web/email/styles.css';
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
const email = document.querySelector('incode-email');
email.config = {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
};
email.onFinish = () => console.log('Email verified!');
email.onError = (err) => console.error('Email error:', err);
</script>
React
React 18 or earlier: add the one-time JSX augmentation from Framework Integration → TypeScript: JSX support for
incode-*tags. React 19+ doesn't need it, and can also use the simpler form from Framework Integration → React 19+ shortcut.
import { useEffect, useRef } from 'react';
import { setup } from '@incodetech/core';
import type { EmailConfig } from '@incodetech/core/email';
import '@incodetech/web/email';
import '@incodetech/web/email/styles.css';
type EmailElement = HTMLElement & {
config: EmailConfig;
onFinish: () => void;
onError: (error: string) => void;
};
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
export function EmailVerification() {
const ref = useRef<EmailElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
el.config = {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
};
el.onFinish = () => console.log('Email verified!');
el.onError = (err) => console.error('Email error:', err);
}, []);
return <incode-email ref={ref} />;
}
For Angular (CUSTOM_ELEMENTS_SCHEMA) and Vue (compilerOptions.isCustomElement) setup, see Framework Integration.
Headless Mode
For complete UI control, use the createEmailManager from @incodetech/core/email.
Quick Start
import { setup } from '@incodetech/core';
import { createEmailManager } from '@incodetech/core/email';
// Initialize SDK first
await setup({
apiURL: 'https://demo-api.incodesmile.com',
token: 'your-session-token',
});
const manager = createEmailManager({
config: {
otpVerification: true,
otpExpirationInMinutes: 5,
prefill: false,
},
});
manager.subscribe((state) => {
console.log('Status:', state.status);
if (state.status === 'finished') {
console.log('Email verified!');
manager.stop();
}
});
// Start the flow
manager.load();
// When state is 'inputting', set the email and submit
manager.setEmail('user@example.com', true);
manager.submit();
// When state is 'awaitingOtp', submit the OTP code
manager.submitOtp('ABC123');
State Machine Flow
flowchart LR
idle -->|load| inputting
inputting -->|submit| awaitingOtp
awaitingOtp -->|submitOtp| finished
awaitingOtp -.->|back| inputting
awaitingOtp -.->|resendOtp| awaitingOtp
States Reference
| Status | Description | Key Properties |
|---|---|---|
idle |
Initial state, waiting for load() |
– |
loadingPrefill |
Fetching pre-filled email from backend | – |
inputting |
Ready for email input | prefilledEmail?, emailError?, otpVerification? |
submitting |
Submitting email to backend | – |
sendingInitialOtp |
Sending OTP code via email for the first time | – |
resendingOtp |
Resending OTP code via email | – |
awaitingOtp |
Waiting for user to enter OTP | resendTimer, canResend, attemptsRemaining |
verifyingOtp |
Verifying OTP code with backend | resendTimer, canResend |
otpError |
OTP verification failed | otpError, attemptsRemaining, resendTimer, canResend |
finished |
Verification complete | – |
error |
Fatal error occurred | error |
State Properties
When status === 'inputting':
| Property | Type | Description |
|---|---|---|
prefilledEmail |
string? |
Pre-populated email address (if prefill: true) |
emailError |
string? |
Validation error from server |
When status === 'awaitingOtp':
| Property | Type | Description |
|---|---|---|
resendTimer |
number |
Seconds remaining before resend allowed |
canResend |
boolean |
Whether resend button should be enabled |
attemptsRemaining |
number |
OTP verification attempts left |
When status === 'otpError':
| Property | Type | Description |
|---|---|---|
otpError |
string |
Error message describing failure |
attemptsRemaining |
number |
Remaining attempts before lockout |
resendTimer |
number |
Seconds remaining before resend allowed |
canResend |
boolean |
Whether resend button should be enabled |
API Methods
| Method | Description | When to Use |
|---|---|---|
load() |
Initializes the flow | Always call first |
setEmail(email, isValid) |
Sets email address and validation state | When inputting, before submit() |
submit() |
Submits the email address | After setting valid email |
setOtpCode(code) |
Sets OTP without submitting | When awaitingOtp (controlled input) |
submitOtp(code) |
Sets and submits OTP code | When awaitingOtp or otpError |
resendOtp() |
Requests new OTP code | When canResend is true |
back() |
Returns to email input | When awaitingOtp |
reset() |
Resets to initial state | After finished or error |
stop() |
Cleanup resources | When unmounting |
getState() |
Returns current state synchronously | Anytime |
subscribe(callback) |
Subscribe to state changes | Returns unsubscribe function |
React Example
import { useState, useEffect } from 'react';
import { createEmailManager, type EmailState } from '@incodetech/core/email';
function CustomEmailVerification() {
const [manager] = useState(() => createEmailManager({
config: { otpVerification: true, otpExpirationInMinutes: 5, prefill: false },
}));
const [state, setState] = useState<EmailState>({ status: 'idle' });
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
useEffect(() => {
const unsubscribe = manager.subscribe(setState);
manager.load();
return () => { unsubscribe(); manager.stop(); };
}, [manager]);
const handleSubmitEmail = () => {
// Simple email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(email);
manager.setEmail(email, isValid);
manager.submit();
};
switch (state.status) {
case 'inputting':
return (
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email address"
/>
{state.emailError && <p className="error">{state.emailError}</p>}
<button onClick={handleSubmitEmail}>Send OTP</button>
</div>
);
case 'submitting':
case 'sendingInitialOtp':
case 'resendingOtp':
return <div>Sending OTP...</div>;
case 'awaitingOtp':
return (
<div>
<p>Enter the 6-character code sent to your email</p>
<input
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value.toUpperCase())}
placeholder="ABC123"
maxLength={6}
/>
<button onClick={() => manager.submitOtp(otp)}>Verify</button>
{state.canResend ? (
<button onClick={() => manager.resendOtp()}>Resend Code</button>
) : (
<p>Resend available in {state.resendTimer}s</p>
)}
<button onClick={() => manager.back()}>Change Email</button>
</div>
);
case 'otpError':
return (
<div>
<p className="error">{state.otpError}</p>
<p>Attempts remaining: {state.attemptsRemaining}</p>
<input
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value.toUpperCase())}
maxLength={6}
/>
<button onClick={() => manager.submitOtp(otp)}>Try Again</button>
</div>
);
case 'verifyingOtp':
return <div>Verifying...</div>;
case 'finished':
return <div>Email verified successfully!</div>;
case 'error':
return <div className="error">Error: {state.error}</div>;
default:
return <div>Loading...</div>;
}
}
Configuration Options
EmailConfig shape:
| Option | Type | Required | Description |
|---|---|---|---|
otpVerification |
boolean |
✅ | Require OTP (email code) verification. If false, the email is verified immediately after submission. |
otpExpirationInMinutes |
number |
✅ | How long the OTP code remains valid. After expiration the user must request a new code. |
prefill |
boolean |
❌ | Pre-populate with the user's previously stored email. Useful for returning users. Default false. |
maxOtpAttempts |
number |
❌ | Maximum OTP verification attempts before lockout. Default 3. |
Troubleshooting
OTP Not Received
- Check the spam/junk folder
- Verify the email address is correct
- Some corporate email filters may block OTP emails
- Add the sender to your contacts/whitelist
Invalid OTP Error
- OTPs expire after the configured time (default 5 minutes)
- Each OTP can only be used once
- Request a new OTP if the current one expires
- Check
attemptsRemainingbefore lockout
Email Address Rejected
- Ensure the email format is valid
- Some disposable email domains may be blocked
- Corporate email addresses are generally accepted
See Also
- Headless Mode: Complete headless API reference
- Individual Modules: Overview of all modules