Integrating a Quiz Form with 1C-Bitrix Email Newsletters
Email marketing built on quiz results is significantly more effective than standard mass mailings — because segmentation is based on the user's actual preferences as expressed in their answers. A client who answered "Interested in bathroom renovation" is placed in the bathroom segment. One who answered "Budget over 500,000" receives emails about premium materials. In 1C-Bitrix, the subscribe module handles newsletters, and since Bitrix version 20+, there is a built-in Email Marketing module. The goal is to connect quiz results to this system.
Newsletter Modules in Bitrix
The subscribe module — the classic option, available in all editions. Tables: b_subscribe, b_subscribe_subscr, b_subscribe_rubric. Works through components bitrix:subscribe.form and bitrix:subscribe.sender.
Email Marketing (the sender module) — more modern, supports segments, automations, and templates. Tables: b_sender_contact, b_sender_mailing_chain, b_sender_mailing_chain_segment. Requires the "Small Business" edition or higher.
For new projects, the sender module is recommended — its API is richer.
Collecting Quiz Data and Subscribing via the sender Module
When a quiz is completed, you need to:
- Add the subscriber to the
sendermodule - Add them to the appropriate segment(s) based on answers
- Trigger an automation (welcome email series)
namespace Local\Quiz;
use Bitrix\Sender\ContactTable;
use Bitrix\Sender\SegmentTable;
class EmailSubscribeHandler
{
/**
* @param array $quizData ['email', 'name', 'answers' => [...], 'result' => '...']
*/
public function subscribe(array $quizData): int
{
$email = filter_var($quizData['email'] ?? '', FILTER_VALIDATE_EMAIL);
if (!$email) {
throw new \InvalidArgumentException('Invalid email: ' . htmlspecialchars($quizData['email'] ?? ''));
}
// Add or update contact in sender
$contactId = $this->upsertContact($email, $quizData['name'] ?? '');
// Determine segments based on quiz answers
$segments = $this->resolveSegments($quizData['answers'] ?? [], $quizData['result'] ?? '');
foreach ($segments as $segmentId) {
$this->addContactToSegment($contactId, $segmentId);
}
// Trigger the welcome sequence
$chainId = $this->getWelcomeChainByResult($quizData['result'] ?? '');
if ($chainId) {
$this->triggerChain($contactId, $chainId);
}
return $contactId;
}
private function upsertContact(string $email, string $name): int
{
// Look for existing contact
$existing = ContactTable::getList([
'filter' => ['=EMAIL' => $email],
'select' => ['ID'],
'limit' => 1,
])->fetch();
if ($existing) {
// Update name if changed
ContactTable::update($existing['ID'], [
'NAME' => $name ?: null,
]);
return (int)$existing['ID'];
}
// Create new contact
$result = ContactTable::add([
'EMAIL' => $email,
'NAME' => $name,
'TYPE_ID' => 'EMAIL',
'CONFIRMED' => 'N', // double opt-in required
]);
if (!$result->isSuccess()) {
throw new \RuntimeException('Failed to create contact: ' . implode(', ', $result->getErrorMessages()));
}
// Send double opt-in email
$this->sendConfirmationEmail($result->getId(), $email);
return $result->getId();
}
private function resolveSegments(array $answers, string $result): array
{
$segmentIds = [];
// Base segment: everyone who completed the quiz
$segmentIds[] = $this->getOrCreateSegment('quiz_all', 'All quiz completers');
// Segmentation by quiz result
$resultSegmentMap = [
'Standard Package' => 'quiz_result_standard',
'Premium Package' => 'quiz_result_premium',
'Economy Package' => 'quiz_result_economy',
];
if (isset($resultSegmentMap[$result])) {
$segmentIds[] = $this->getOrCreateSegment(
$resultSegmentMap[$result],
'Quiz: ' . $result
);
}
// Segmentation by specific answers
foreach ($answers as $answer) {
$tag = $this->answerToSegmentTag($answer['question'] ?? '', $answer['answer'] ?? '');
if ($tag) {
$segmentIds[] = $this->getOrCreateSegment($tag['code'], $tag['name']);
}
}
return array_unique(array_filter($segmentIds));
}
private function answerToSegmentTag(string $question, string $answer): ?array
{
$mapping = [
'Room type' => [
'Apartment' => ['code' => 'room_apartment', 'name' => 'Type: Apartment'],
'House' => ['code' => 'room_house', 'name' => 'Type: House'],
'Office' => ['code' => 'room_office', 'name' => 'Type: Office'],
],
'Budget' => [
'Up to 50,000 RUB' => ['code' => 'budget_low', 'name' => 'Budget: low'],
'50,000 – 100,000 RUB' => ['code' => 'budget_mid', 'name' => 'Budget: mid'],
'Over 100,000 RUB' => ['code' => 'budget_high', 'name' => 'Budget: high'],
],
];
return $mapping[$question][$answer] ?? null;
}
private function getOrCreateSegment(string $code, string $name): int
{
$existing = SegmentTable::getList([
'filter' => ['=CODE' => $code],
'select' => ['ID'],
'limit' => 1,
])->fetch();
if ($existing) {
return (int)$existing['ID'];
}
$result = SegmentTable::add([
'CODE' => $code,
'NAME' => $name,
'ACTIVE' => 'Y',
]);
return $result->getId();
}
private function addContactToSegment(int $contactId, int $segmentId): void
{
\Bitrix\Sender\ContactSegmentTable::add([
'CONTACT_ID' => $contactId,
'SEGMENT_ID' => $segmentId,
]);
}
}
Automated Email Sequence Based on Quiz Result
In the Bitrix Email Marketing module, create sequences (Automation):
- Module → Marketing → Automation → Create Sequence
Trigger — adding a contact to a segment. Via the API, trigger it manually:
private function triggerChain(int $contactId, int $chainId): void
{
// Add contact to the mailing sequence
\Bitrix\Sender\MailingChainTable::addContact($chainId, $contactId);
}
private function getWelcomeChainByResult(string $result): ?int
{
// Mapping quiz results → Bitrix sender chain IDs
$chainMap = [
'Standard Package' => 5,
'Premium Package' => 6,
'Economy Package' => 7,
];
return $chainMap[$result] ?? null; // null — use the default chain
}
Double Opt-In: A Mandatory Component
Under Russian law (Federal Law No. 38 "On Advertising") and GDPR-compatible practices, email confirmation is required before sending newsletters.
private function sendConfirmationEmail(int $contactId, string $email): void
{
// Generate confirmation token
$token = bin2hex(random_bytes(32));
// Save token in a Highload block or table
$this->saveConfirmToken($contactId, $token);
// Build confirmation link
$confirmUrl = 'https://example.com/subscribe/confirm/?token=' . $token;
// Send via the main module (mail event)
\CEvent::Send('QUIZ_SUBSCRIBE_CONFIRM', SITE_ID, [
'EMAIL' => $email,
'CONFIRM_URL' => $confirmUrl,
]);
}
Confirmation page: /subscribe/confirm/?token=... — activates the contact (CONFIRMED = Y) and redirects to a thank-you page.
Integration with the Classic subscribe Module
For Bitrix versions without the sender module — use b_subscribe_subscr:
// Subscribing via the legacy module
\CModule::IncludeModule('subscribe');
$subscriber = new \CSubscribe();
$subscribeId = $subscriber->Add([
'FORMAT' => 'html',
'EMAIL' => $email,
'ACTIVE' => 'Y',
'CONFIRMED' => 'N',
]);
// Subscribe to rubrics corresponding to quiz segments
$rubricIds = $this->resolveRubricIds($quizAnswers);
foreach ($rubricIds as $rubricId) {
$subscriber->Subscribe($subscribeId, $rubricId, SITE_ID);
}
Scope of Work
- Choose the newsletter module (sender vs. subscribe) based on the Bitrix edition
- Webhook handler for the quiz platform or integration of a custom quiz
- Logic for mapping answers → segments/rubrics
- Double opt-in: email template, confirmation page
- Welcome email templates for each quiz result
- Creating automations/sequences in Email Marketing
Timeline: basic integration (subscription + one segment) — 3–5 days. Full system with multi-segmentation and sequences — 2–3 weeks.







