SDK reference · Incode Web SDK 2 Reference / Web SDK 2 Individual Modules

Email Module

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

Was this page helpful?