Development of a 1C-Bitrix mailing module

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

Development of Mailing Module for 1C-Bitrix

The built-in mailing module in 1C-Bitrix (subscribe) has existed for a long time, but in real-world projects it quickly hits its limitations: lack of flexible segmentation, no event triggers, difficult integration with external SMTP providers. When business needs outgrow the standard solution — you build a custom module.

Problems with the Standard subscribe Module

The standard module stores subscribers in the b_subscribe_subscr_addr table and email templates in b_subscribe_posting. The sending logic operates through CAgent agents, which run synchronously during HTTP requests. With a database of 10,000+ contacts, this leads to timeouts and missed messages.

Additionally: no built-in open tracking, no retry queue for SMTP errors, no support for transactional emails with dynamic content via API.

Custom Module Architecture

The module registers in the standard way via /bitrix/modules/vendor.mailer/install/index.php with the vendor_mailer class. The namespace is \Vendor\Mailer.

Key components:

  • SubscriberTable — ORM table b_vendor_mailer_subscriber (id, email, name, status, groups, created_at, unsubscribed_at)
  • CampaignTableb_vendor_mailer_campaign (id, name, subject, template_id, status, scheduled_at, sent_count, open_count)
  • QueueTableb_vendor_mailer_queue (id, campaign_id, subscriber_id, status, attempts, last_error, sent_at)
  • EventTableb_vendor_mailer_event (id, subscriber_id, type, payload, created_at) — tracking opens and clicks

Tables are created via \Bitrix\Main\ORM\Data\DataManager with full D7-ORM support.

Sending Queue

Synchronous sending via CAgent is inefficient. We use a queue instead:

// Enqueueing when campaign starts
public function enqueue(int $campaignId): void
{
    $subscribers = SubscriberTable::getList([
        'filter' => ['=STATUS' => 'active', '=GROUPS' => $this->campaign->getGroups()],
        'select' => ['ID', 'EMAIL', 'NAME'],
    ]);

    while ($row = $subscribers->fetch()) {
        QueueTable::add([
            'CAMPAIGN_ID'   => $campaignId,
            'SUBSCRIBER_ID' => $row['ID'],
            'STATUS'        => 'pending',
            'ATTEMPTS'      => 0,
        ]);
    }
}

The queue processor is launched by an agent every 2 minutes, takes a batch of 100 records with pending status, sends them through the selected transport, and updates the status to sent or failed.

Transports: SMTP and API Providers

The transport abstraction is implemented via the MailTransportInterface:

interface MailTransportInterface
{
    public function send(Message $message): SendResult;
}

Concrete implementations: SmtpTransport (via PHPMailer or native mail()), SendGridTransport (REST API v3), MailgunTransport, AmazonSesTransport. The transport is selected in the module settings (b_option, namespace vendor.mailer).

On send error, the status transitions to failed, and the attempts counter increments. When attempts >= 3, the record is marked as dead and is not processed again.

Open and Click Tracking

A transparent 1×1 pixel is inserted into the email:

<img src="https://site.ru/mailer/track/open/?uid=UNIQUE_TOKEN" width="1" height="1">

On GET request, the controller logs the event in b_vendor_mailer_event and returns a 1×1 GIF. Clicks are tracked via redirect: all links in the email are replaced with https://site.ru/mailer/track/click/?uid=TOKEN&url=ENCODED_URL.

Event handler:

EventTable::add([
    'SUBSCRIBER_ID' => $subscriberId,
    'CAMPAIGN_ID'   => $campaignId,
    'TYPE'          => 'open', // or 'click'
    'PAYLOAD'       => json_encode(['url' => $url, 'ip' => $_SERVER['REMOTE_ADDR']]),
    'CREATED_AT'    => new \Bitrix\Main\Type\DateTime(),
]);

Subscriber Segmentation

Groups are stored in b_vendor_mailer_group, with subscriber relationships in b_vendor_mailer_subscriber_group. Segmentation supports conditions based on Bitrix user profile fields (b_user) via JOIN.

Dynamic segments are built through a filter builder in the administrative interface — the same approach as in Bitrix CRM: a set of conditions with AND/OR operators, which are translated into ORM filters when enqueueing.

Transactional Emails

For system emails (email confirmation, password reset, order notifications), the module provides an event API:

// Sending transactional email
\Vendor\Mailer\Transactional::send('order_confirmed', [
    'USER_ID'    => $userId,
    'ORDER_ID'   => $orderId,
    'ORDER_SUM'  => $orderSum,
    'ITEMS'      => $orderItems,
]);

Transactional email templates are stored in b_vendor_mailer_template and edited through the administrative section with support for variables in the format {{ORDER_ID}}.

Administrative Interface

The module adds sections to /bitrix/admin/:

  • vendor_mailer_subscribers.php — subscriber list and management
  • vendor_mailer_campaigns.php — campaigns, status, statistics
  • vendor_mailer_templates.php — template editor
  • vendor_mailer_settings.php — transport settings, DKIM, limits

The interface is built on standard classes CAdminList, CAdminForm — fully compatible with Bitrix theme styling.

Unsubscribe and GDPR

An unsubscribe link in each email leads to https://site.ru/mailer/unsubscribe/?token=TOKEN. Upon clicking, the subscriber's status changes to unsubscribed, and the unsubscribed_at field is populated. Re-enqueueing for such records is blocked at the filter level.

For GDPR compliance: export subscriber data on request (GET /mailer/export-personal/?token=TOKEN), complete deletion via SubscriberTable::delete() with cascading deletion from all related tables.

Development Timeline

Stage Duration
Module structure, ORM tables, installer 2 days
Queue and sending agents 2 days
SMTP transport + 1 API provider 1 day
Open and click tracking 1 day
Segmentation, groups 1 day
Transactional emails 1 day
Administrative interface 2 days
Unsubscribe, GDPR, testing 1 day

Total: 11 working days for basic version. Connecting additional API providers (Mailchimp, UniSender) — +1 day each.