Setting up email verification when ordering 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Setting Up Email Verification at Checkout in 1C-Bitrix

A fake email on an order means a lost customer contact, a tracking email bounced to nowhere, and sometimes a signal of a fraudulent order. Email verification during checkout confirms that the address exists and belongs to the user. For guest purchases, this is especially important.

Two Levels of Verification

Syntax check — minimal validation of the format and the domain's DNS MX record. Fast, requires no email sending, catches typos and non-existent domains.

Full verification via link — we send an email with a link, the user clicks it and confirms. Reliable, but requires an action from the user.

For orders, the first level is usually sufficient, with the option to enable the second for suspicious addresses.

Syntax Check + MX

namespace Local\Validation;

class EmailValidator
{
    public static function validate(string $email): ValidationResult
    {
        // Format
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return new ValidationResult(false, 'Invalid email format');
        }

        $domain = strtolower(substr(strrchr($email, '@'), 1));

        // Disposable domains
        if (self::isDisposable($domain)) {
            return new ValidationResult(false, 'Disposable email addresses are not accepted');
        }

        // MX record check
        if (!self::hasMxRecord($domain)) {
            return new ValidationResult(false, 'Domain does not accept email');
        }

        return new ValidationResult(true, '');
    }

    private static function isDisposable(string $domain): bool
    {
        $disposable = [
            'mailinator.com', 'guerrillamail.com', 'tempmail.com',
            'throwam.com', 'yopmail.com', '10minutemail.com',
            'trashmail.com', 'dispostable.com', 'fakeinbox.com',
            'getairmail.com', 'sharklasers.com',
        ];
        return in_array($domain, $disposable, true);
    }

    private static function hasMxRecord(string $domain): bool
    {
        // getmxrr is reliable but requires a DNS resolver
        // Timeout ~1-2 sec when DNS is unavailable
        return (bool)@getmxrr($domain, $mxHosts);
    }
}

Applying Validation in the Order Handler

AddEventHandler('sale', 'OnBeforeOrderFinalAction', function(\Bitrix\Sale\Order $order) {
    if ($order->getId() > 0) return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);

    $props = $order->getPropertyCollection();
    $email = trim($props->getItemByOrderPropertyCode('EMAIL')?->getValue() ?? '');

    if (empty($email)) {
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::ERROR,
            new \Bitrix\Main\Error('Please provide an email to receive the order confirmation')
        );
    }

    $result = \Local\Validation\EmailValidator::validate($email);

    if (!$result->isValid()) {
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::ERROR,
            new \Bitrix\Main\Error($result->getError())
        );
    }

    return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
});

Full Verification via Link (for Authenticated Users)

On registration or first order — send a link with a token:

namespace Local\Validation;

class EmailVerificationService
{
    public static function sendVerification(int $userId, string $email): bool
    {
        $token = bin2hex(random_bytes(32));
        $exp   = time() + 86400; // 24 hours

        // Save the token
        $hlEntity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity(
            \Bitrix\Highloadblock\HighloadBlockTable::getById(EMAIL_VERIFY_HLBLOCK_ID)->fetch()
        );
        $hlEntity->getDataClass()::add([
            'UF_USER_ID'   => $userId,
            'UF_EMAIL'     => $email,
            'UF_TOKEN'     => $token,
            'UF_EXPIRES'   => \Bitrix\Main\Type\DateTime::createFromTimestamp($exp),
            'UF_CONFIRMED' => false,
        ]);

        // Send the email
        $verifyUrl = 'https://' . SITE_SERVER_NAME . '/local/verify-email.php?token=' . $token;

        return \CEvent::Send('EMAIL_VERIFICATION', SITE_ID, [
            'EMAIL'      => $email,
            'VERIFY_URL' => $verifyUrl,
            'EXPIRES_AT' => date('d.m.Y H:i', $exp),
        ]);
    }

    public static function confirmToken(string $token): bool
    {
        $hlEntity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity(
            \Bitrix\Highloadblock\HighloadBlockTable::getById(EMAIL_VERIFY_HLBLOCK_ID)->fetch()
        );
        $dataClass = $hlEntity->getDataClass();

        $row = $dataClass::getRow([
            'filter' => [
                'UF_TOKEN'     => $token,
                'UF_CONFIRMED' => false,
                '>=UF_EXPIRES' => new \Bitrix\Main\Type\DateTime(),
            ],
            'select' => ['ID', 'UF_USER_ID'],
        ]);

        if (!$row) return false;

        $dataClass::update($row['ID'], ['UF_CONFIRMED' => true]);

        // Update the flag in the user profile
        \CUser::Update($row['UF_USER_ID'], ['UF_EMAIL_VERIFIED' => 'Y']);

        return true;
    }
}

Email Confirmation Page

// /local/verify-email.php
\Bitrix\Main\Application::getInstance()->initializeExtended();

$token  = trim($_GET['token'] ?? '');
$result = false;

if (strlen($token) === 64 && preg_match('/^[a-f0-9]+$/', $token)) {
    $result = \Local\Validation\EmailVerificationService::confirmToken($token);
}

// Redirect with message
if ($result) {
    LocalRedirect('/personal/?email_verified=1');
} else {
    LocalRedirect('/personal/?email_verify_error=1');
}

Client-Side Validation

Instant feedback before form submission — check syntax and show a hint:

const emailInput = document.querySelector('[name="ORDER_EMAIL"]');

emailInput?.addEventListener('blur', async () => {
    const email = emailInput.value.trim();
    if (!email) return;

    // Client-side syntax check
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
        showFieldError(emailInput, 'Please check your email address');
        return;
    }

    // Server-side check (MX + disposable)
    const res = await fetch('/local/ajax/validate-email.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
    }).then(r => r.json());

    if (!res.valid) {
        showFieldError(emailInput, res.error);
    } else {
        clearFieldError(emailInput);
    }
});
// /local/ajax/validate-email.php
\Bitrix\Main\Application::getInstance()->initializeExtended();

$email  = json_decode(file_get_contents('php://input'), true)['email'] ?? '';
$result = \Local\Validation\EmailValidator::validate($email);

header('Content-Type: application/json');
echo json_encode(['valid' => $result->isValid(), 'error' => $result->getError()]);

Performance

An MX lookup via getmxrr() takes 50–500 ms. With a slow DNS resolver — up to 2 seconds. If this is critical: cache the result by domain for 1 hour, perform the check asynchronously (after the user types the email, not on form submit).

Implementation Timelines

Configuration Timeline
Syntax + disposable domains + MX 1–2 days
+ verification via email link +2–3 days
+ client-side AJAX validation +1 day