Setting Up Registration Confirmation via Email in 1C-Bitrix
The standard registration in 1C-Bitrix by default either does not require email confirmation or uses the built-in mechanism — depending on the settings of the system.auth.registration component. The problem is that the confirmation email is sent via CEvent::Send() with the NEW_USER_CONFIRM template, which looks like a system notification from 2005. Reworking this mechanism to meet modern requirements — transactional email, branded template, proper handling of repeat requests — requires intervention at several points in the system.
How the Standard Mechanism Works
During registration with the EMAIL_CONFIRMATION parameter enabled, the system.auth.registration component creates a user with ACTIVE = N and sends an email via the NEW_USER_CONFIRM event. The email contains a link of the form:
/bitrix/[email protected]&checkword=HASH
When the link is followed, the confirm.php script verifies the CHECKWORD from the b_user table and activates the account (ACTIVE = Y).
CHECKWORD is stored directly in b_user.CHECKWORD — one field for everything. If the user requests a repeat email, the old CHECKWORD is overwritten.
Custom Email Template
The email template is edited in Settings → Mail Events → NEW_USER_CONFIRM. For a branded HTML email:
- Create a template of type
NEW_USER_CONFIRMwith a full HTML body - Use the variables:
#EMAIL#,#LOGIN#,#NAME#,#CONFIRM_CODE#,#SITE_URL# - For correct rendering in email clients — table-based layout, inline styles
If a transactional provider (SendGrid, Mailgun, Postmark) is needed instead of PHP mail() — connect it via the main module. Installing a custom send handler:
// In init.php or a module
AddEventHandler('main', 'OnBeforeEventSend', function(array &$eventFields, array &$message, array $siteData) {
if ($message['EVENT_NAME'] === 'NEW_USER_CONFIRM') {
// Intercept and send via SendGrid API
SendGridMailer::send(
to: $eventFields['EMAIL'],
subject: $message['SUBJECT'],
html: $message['BODY']
);
return false; // Cancel standard send
}
});
Resending the Email
The standard component does not provide a "Resend" button. Adding one:
// /local/components/local/auth.resend-confirm/class.php
if ($_POST['resend_email'] ?? false) {
$email = trim($_POST['email'] ?? '');
$user = \Bitrix\Main\UserTable::getList([
'filter' => ['EMAIL' => $email, 'ACTIVE' => 'N'],
'select' => ['ID', 'LOGIN', 'NAME', 'CHECKWORD', 'CHECKWORD_TIME'],
'limit' => 1,
])->fetch();
if (!$user) {
$this->addError('User not found or already activated');
return;
}
// Rate limit: no more than 1 email in 5 minutes
$lastSent = strtotime($user['CHECKWORD_TIME']);
if (time() - $lastSent < 300) {
$this->addError('Email already sent. Please wait 5 minutes.');
return;
}
// Generate new checkword
$newCheckword = md5(uniqid('', true));
\CUser::Update($user['ID'], ['CHECKWORD' => $newCheckword]);
\CEvent::Send('NEW_USER_CONFIRM', SITE_ID, [
'EMAIL' => $email,
'LOGIN' => $user['LOGIN'],
'NAME' => $user['NAME'],
'CONFIRM_CODE' => $newCheckword,
'SITE_URL' => SITE_SERVER_NAME,
]);
$this->result['SUCCESS'] = true;
}
Link Expiry
By default CHECKWORD has no expiry — a link from a month-old email will still work. To limit the lifespan:
In the handler for confirm.php (or the overridden confirmation component) check CHECKWORD_TIME:
$user = \Bitrix\Main\UserTable::getList([
'filter' => ['LOGIN' => $login, 'CHECKWORD' => $hash, 'ACTIVE' => 'N'],
'select' => ['ID', 'CHECKWORD_TIME'],
])->fetch();
if (!$user) {
ShowError('Link is invalid');
return;
}
$checkwordAge = time() - strtotime($user['CHECKWORD_TIME']);
if ($checkwordAge > 86400 * 3) { // 3 days
ShowError('Link has expired. <a href="/resend-confirm/">Request a new email</a>');
return;
}
\CUser::Confirm($login, $hash);
Auto-deletion of Unactivated Accounts
Users who have not confirmed their email within N days clutter the database. An agent runs daily to delete them:
function DeleteUnconfirmedUsers(): string
{
$cutoffDate = (new \DateTime())->modify('-7 days')->format('Y-m-d H:i:s');
$res = \Bitrix\Main\UserTable::getList([
'filter' => ['ACTIVE' => 'N', '<DATE_REGISTER' => $cutoffDate],
'select' => ['ID'],
]);
while ($row = $res->fetch()) {
\CUser::Delete($row['ID']);
}
return __FUNCTION__ . '();';
}
Register the agent in the module via \Bitrix\Main\Agent.
Scope of Work
- Custom HTML email template
NEW_USER_CONFIRM - Transactional provider connection (optional)
- Resend component with rate limiting
- Confirmation link expiry enforcement
- Agent for auto-deletion of unactivated accounts
Timelines: 3–5 days for a basic rework with a custom template and resend functionality. 1–1.5 weeks when connecting a transactional provider and fully reworking the UX.







