Email Module
This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. Contact your Incode Representative for upgrade information and check if you are a candidate for this upgrade.
Full rollout to all clients still TBD.
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('[email protected]', 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
