Integrating a quiz form with 1C-Bitrix email newsletters

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
    1173
  • 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
    745
  • 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

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:

  1. Add the subscriber to the sender module
  2. Add them to the appropriate segment(s) based on answers
  3. 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.