Email Module

The Email module verifies a user's email address via OTP (One-Time Password).

📘

This guide is specific to Web SDK 2.0. If you are still using 1.x, you can find documentation here. We strongly recommend upgrading - contact your Incode Representative for upgrade information.

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):

PropertyTypeRequiredDescription
configEmailConfigConfiguration options
onFinish() => voidCalled when verification completes
onError(error: string) => voidCalled 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

StatusDescriptionKey Properties
idleInitial state, waiting for load()
loadingPrefillFetching pre-filled email from backend
inputtingReady for email inputprefilledEmail?, emailError?, otpVerification?
submittingSubmitting email to backend
sendingInitialOtpSending OTP code via email for the first time
resendingOtpResending OTP code via email
awaitingOtpWaiting for user to enter OTPresendTimer, canResend, attemptsRemaining
verifyingOtpVerifying OTP code with backendresendTimer, canResend
otpErrorOTP verification failedotpError, attemptsRemaining, resendTimer, canResend
finishedVerification complete
errorFatal error occurrederror

State Properties

When status === 'inputting':

PropertyTypeDescription
prefilledEmailstring?Pre-populated email address (if prefill: true)
emailErrorstring?Validation error from server

When status === 'awaitingOtp':

PropertyTypeDescription
resendTimernumberSeconds remaining before resend allowed
canResendbooleanWhether resend button should be enabled
attemptsRemainingnumberOTP verification attempts left

When status === 'otpError':

PropertyTypeDescription
otpErrorstringError message describing failure
attemptsRemainingnumberRemaining attempts before lockout
resendTimernumberSeconds remaining before resend allowed
canResendbooleanWhether resend button should be enabled

API Methods

MethodDescriptionWhen to Use
load()Initializes the flowAlways call first
setEmail(email, isValid)Sets email address and validation stateWhen inputting, before submit()
submit()Submits the email addressAfter setting valid email
setOtpCode(code)Sets OTP without submittingWhen awaitingOtp (controlled input)
submitOtp(code)Sets and submits OTP codeWhen awaitingOtp or otpError
resendOtp()Requests new OTP codeWhen canResend is true
back()Returns to email inputWhen awaitingOtp
reset()Resets to initial stateAfter finished or error
stop()Cleanup resourcesWhen unmounting
getState()Returns current state synchronouslyAnytime
subscribe(callback)Subscribe to state changesReturns 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:

OptionTypeRequiredDescription
otpVerificationbooleanRequire OTP (email code) verification. If false, the email is verified immediately after submission.
otpExpirationInMinutesnumberHow long the OTP code remains valid. After expiration the user must request a new code.
prefillbooleanPre-populate with the user's previously stored email. Useful for returning users. Default false.
maxOtpAttemptsnumberMaximum 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 attemptsRemaining before 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