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 |







