How to Create a CRM Activity from an Incoming E-mail

Scope: mail

Who can execute the methods: to complete the entire tutorial, the strictest of the listed permissions is required — access to the mailbox where the e-mail is located and access to CRM

Choose a tool for developing with an AI agent:

  • use Alaio Vibecode to build an app for Bitrix24 from a task description without knowing any programming language. The agent writes the code and deploys the app to a server, with no manual hosting setup
  • use the MCP server to develop a REST API integration in your own project. The agent refers to the official REST documentation

An incoming e-mail can be converted into a CRM activity. To do this, find the e-mail in an available mailbox and pass its identifier to the activity creation method.

The mail.message.createcrmactivity method does not accept a lead, deal, contact, or company identifier. It creates a CRM activity from an e-mail, and the link to the CRM object is determined by the e-mail data and CRM settings.

The tutorial consists of four steps.

  1. Retrieve mailboxes using mail.mailbox.list
  2. Find an incoming e-mail using mail.message.list
  3. Create a CRM activity using mail.message.createcrmactivity
  4. Check the e-mail link using mail.message.get

As a result, the e-mail will have a link in the bindings field, and a CRM activity will be created from the e-mail.

What You Need Before You Start

Before running the tutorial, check that:

  • an inbound webhook is created with the mail scope
  • the webhook user has access to the mailbox with the incoming e-mail
  • CRM is enabled and configured, and the webhook user has CRM access
  • the e-mail is available to the current user and has not been deleted
  • the webhook path is stored in an environment variable and contains the /rest/api/ segment

Mail methods belong to REST 3.0. Method call specifics and the JSON request format are described in the REST 3.0 overview. For server-side JS examples, use $b24.actions.v3; for Python, specify prefer_version=3. The PHP SDK does not support calls through /rest/api/, so the PHP example sends a direct HTTP request.

The examples below use an e-mail with the subject "Contract" and the period from August 1 through August 31, 2026. In your Bitrix24, the values will be different: choose the search string and period so that mail.message.list finds the required incoming e-mail.

1. Retrieve Mailboxes

The mail.mailbox.list method returns the current user's mailboxes.

Call the method with the parameter:

  • pagination — pagination settings. In the example, we request the first page and limit the response to 20 mailboxes

How to Use Examples in Documentation

import { B24Hook, Text } from '@bitrix24/b24jssdk'
        
        const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
        // B24_HOOK = 'https://your-domain.bitrix24.com/rest/api/USER_ID/TOKEN/'
        
        async function callMethod(method, params) {
            const response = await $b24.actions.v3.call.make({
                method,
                params,
                requestId: Text.getUuidRfc4122()
            })
        
            if (!response.isSuccess) {
                throw new Error(response.getErrorMessages().join('; '))
            }
        
            return response.getData().result
        }
        
        const mailboxesResult = await callMethod('mail.mailbox.list', {
            pagination: {
                page: 1,
                limit: 20,
                offset: 0
            }
        })
        
        const mailbox = mailboxesResult.items[0]
        if (!mailbox) {
            throw new Error('No available mailboxes')
        }
        
        const mailboxId = mailbox.id
        
<?php
        
        $webhook = getenv('B24_HOOK');
        // B24_HOOK = 'https://your-domain.bitrix24.com/rest/api/USER_ID/TOKEN/'
        
        function callMethod(string $webhook, string $method, array $params)
        {
            $ch = curl_init($webhook . $method);
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
                CURLOPT_POSTFIELDS => json_encode($params, JSON_UNESCAPED_UNICODE),
                CURLOPT_RETURNTRANSFER => true,
            ]);
        
            $response = curl_exec($ch);
            if ($response === false)
            {
                throw new RuntimeException(curl_error($ch));
            }
        
            $data = json_decode($response, true);
            if (isset($data['error']))
            {
                throw new RuntimeException($data['error']['message']);
            }
        
            return $data['result'];
        }
        
        $mailboxesResult = callMethod($webhook, 'mail.mailbox.list', [
            'pagination' => [
                'page' => 1,
                'limit' => 20,
                'offset' => 0,
            ],
        ]);
        
        $mailbox = $mailboxesResult['items'][0] ?? null;
        if (!$mailbox)
        {
            throw new RuntimeException('No available mailboxes');
        }
        
        $mailboxId = $mailbox['id'];
        
import os
        
        from b24pysdk import BitrixWebhook, Client
        
        client = Client(
            BitrixWebhook(
                domain="your-domain.bitrix24.com",
                webhook_token=os.environ["B24_HOOK_TOKEN"],
            ),
            prefer_version=3,
        )
        # B24_HOOK_TOKEN = 'user_id/webhook_key'
        
        mailboxes_result = client.mail.mailbox.list(
            pagination={
                "page": 1,
                "limit": 20,
                "offset": 0,
            },
        ).response.result
        
        if not mailboxes_result["items"]:
            raise RuntimeError("No available mailboxes")
        
        mailbox_id = mailboxes_result["items"][0]["id"]
        

As a result, you receive a list of mailboxes. For the next step, save the id of the mailbox where you need to find the incoming e-mail.

{
            "result": {
                "items": [
                    {
                        "id": 1,
                        "name": "Work mail",
                        "email": "user@example.com",
                        "senderName": "Klaus Weber"
                    }
                ]
            }
        }
        

2. Find the Incoming E-mail

The mail.message.list method returns e-mails by conditions. In the example, we search for an e-mail in the selected mailbox by subject and period.

Use the method with the parameters:

  • mailboxId — mailbox identifier from step 1
  • searchQuery — e-mail search string. In the example, we search for e-mails by the word contract
  • dateFrom and dateTo — boundaries of the period where the e-mail must be found
  • pagination — pagination settings. In the example, we request the first page and limit the response to 20 e-mails
const messagesResult = await callMethod('mail.message.list', {
            mailboxId,
            searchQuery: 'contract',
            dateFrom: '2026-08-01T00:00:00+02:00',
            dateTo: '2026-08-31T23:59:59+02:00',
            pagination: {
                page: 1,
                limit: 20,
                offset: 0
            }
        })
        
        const message = messagesResult.items[0]
        if (!message) {
            throw new Error('E-mail not found')
        }
        
        const messageId = message.id
        
$messagesResult = callMethod($webhook, 'mail.message.list', [
            'mailboxId' => $mailboxId,
            'searchQuery' => 'contract',
            'dateFrom' => '2026-08-01T00:00:00+02:00',
            'dateTo' => '2026-08-31T23:59:59+02:00',
            'pagination' => [
                'page' => 1,
                'limit' => 20,
                'offset' => 0,
            ],
        ]);
        
        $message = $messagesResult['items'][0] ?? null;
        if (!$message)
        {
            throw new RuntimeException('E-mail not found');
        }
        
        $messageId = $message['id'];
        
messages_result = client.mail.message.list(
            mailbox_id=mailbox_id,
            search_query="contract",
            date_from="2026-08-01T00:00:00+02:00",
            date_to="2026-08-31T23:59:59+02:00",
            pagination={
                "page": 1,
                "limit": 20,
                "offset": 0,
            },
        ).response.result
        
        if not messages_result["items"]:
            raise RuntimeError("E-mail not found")
        
        message_id = messages_result["items"][0]["id"]
        

As a result, you receive a list of e-mails. For the next step, save the id of the required e-mail in the messageId variable.

{
            "result": {
                "items": [
                    {
                        "id": 15,
                        "mailboxId": 1,
                        "mailboxEmail": "user@example.com",
                        "subject": "Contract",
                        "from": "client@example.com",
                        "to": "user@example.com",
                        "date": "2026-08-15T10:00:00+02:00",
                        "bindings": []
                    }
                ]
            }
        }
        

3. Create a CRM Activity

The mail.message.createcrmactivity method creates a CRM activity from an e-mail.

Use the method with the parameter:

  • messageId — e-mail identifier saved from the mail.message.list response in step 2
const createResult = await callMethod('mail.message.createcrmactivity', {
            messageId
        })
        
        console.log(createResult)
        
$createResult = callMethod($webhook, 'mail.message.createcrmactivity', [
            'messageId' => $messageId,
        ]);
        
        print_r($createResult);
        
create_result = client.mail.message.createcrmactivity(
            message_id=message_id,
        ).response.result
        
        print(create_result)
        

A successful response contains an object with result: true.

{
            "result": {
                "result": true
            }
        }
        

The mail.message.get method returns an e-mail by identifier.

Use the method with the parameters:

  • id — e-mail identifier messageId saved from the mail.message.list response in step 2
  • select — list of fields to retrieve. Request the bindings field to see the created link
const messageResult = await callMethod('mail.message.get', {
            id: messageId,
            select: [
                'id',
                'subject',
                'from',
                'to',
                'bindings',
                'url'
            ]
        })
        
        console.log(messageResult.item.bindings)
        
$messageResult = callMethod($webhook, 'mail.message.get', [
            'id' => $messageId,
            'select' => [
                'id',
                'subject',
                'from',
                'to',
                'bindings',
                'url',
            ],
        ]);
        
        print_r($messageResult['item']['bindings']);
        
message_result = client.mail.message.get(
            bitrix_id=message_id,
            select=[
                "id",
                "subject",
                "from",
                "to",
                "bindings",
                "url",
            ],
        ).response.result
        
        print(message_result["item"]["bindings"])
        

The CRM link is displayed in the bindings array. The response is shortened to the fields required for verification.

{
            "result": {
                "item": {
                    "id": 15,
                    "subject": "Contract",
                    "from": "client@example.com",
                    "to": "user@example.com",
                    "url": "/mail/message/15",
                    "bindings": [
                        {
                            "type": "crm",
                            "entityTypeId": 3,
                            "entityId": 125
                        }
                    ]
                }
            }
        }
        

Verify the Result

Open the e-mail in Bitrix24 Mail. The e-mail must have a CRM link.

Through REST, the tutorial is complete if the mail.message.get method returns a non-empty bindings array and it contains an object with type: "crm".

Errors and Diagnostics

If the method returns an error, check the request data.

Code

Reason and Action

BITRIX_REST_V3_EXCEPTION_INSUFFICIENTSCOPEEXCEPTION

The webhook or application does not have the mail scope. Add the scope and repeat the request

BITRIX_REST_V3_EXCEPTION_ACCESSDENIEDEXCEPTION

The user does not have access to the mailbox or e-mail. Check the webhook user

BITRIX_REST_V3_EXCEPTION_VALIDATION_REQUESTVALIDATIONEXCEPTION

An empty or invalid value was passed in messageId, or the e-mail cannot be saved to CRM. Pass a positive integer and select an e-mail that can be linked to CRM

BITRIX_REST_V3_EXCEPTION_ENTITYNOTFOUNDEXCEPTION

The e-mail was not found. Check mailboxId, the search filter, and the e-mail identifier

MESSAGE_LIST_FAILED

The e-mail search conditions did not pass validation. Check the dateFrom and dateTo format

If the mail.message.createcrmactivity method returns an object with result: true, but bindings is empty, check CRM settings and e-mail data:

  • the webhook user has CRM access
  • CRM tracker or e-mail processing in CRM is configured for the address from the e-mail
  • the sender or recipient address of the e-mail matches an e-mail in a lead, contact, or company
  • the e-mail has not been deleted and is available in the active mailbox connection

The method does not accept a target CRM object manually, so the link depends on CRM e-mail processing.

Key Considerations

Consider the tutorial limitations:

  • mail.message.createcrmactivity creates a CRM activity from an existing e-mail and does not send a new e-mail
  • the messageId parameter of the mail.message.createcrmactivity method is taken from the mail.message.list or mail.message.get response
  • the target CRM object cannot be passed as a parameter: mail.message.createcrmactivity has no fields for a lead, deal, contact, or company identifier
  • calling mail.message.createcrmactivity again for the same e-mail may return an error or leave the existing link unchanged; check bindings before retrying
  • the link can be deleted using mail.message.removecrmactivity

Code Example

The code combines all steps: retrieves a mailbox, searches for an e-mail, creates a CRM activity, and checks bindings. Replace the search string and period with your own values.

import { B24Hook, Text } from '@bitrix24/b24jssdk'
        
        const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
        
        async function callMethod(method, params) {
            const response = await $b24.actions.v3.call.make({
                method,
                params,
                requestId: Text.getUuidRfc4122()
            })
        
            if (!response.isSuccess) {
                throw new Error(response.getErrorMessages().join('; '))
            }
        
            return response.getData().result
        }
        
        const mailboxes = await callMethod('mail.mailbox.list', {
            pagination: { page: 1, limit: 20, offset: 0 }
        })
        const mailbox = mailboxes.items[0]
        if (!mailbox) {
            throw new Error('No available mailboxes')
        }
        const mailboxId = mailbox.id
        
        const messages = await callMethod('mail.message.list', {
            mailboxId,
            searchQuery: 'contract',
            dateFrom: '2026-08-01T00:00:00+02:00',
            dateTo: '2026-08-31T23:59:59+02:00',
            pagination: { page: 1, limit: 20, offset: 0 }
        })
        const sourceMessage = messages.items[0]
        if (!sourceMessage) {
            throw new Error('E-mail not found')
        }
        const messageId = sourceMessage.id
        
        await callMethod('mail.message.createcrmactivity', { messageId })
        
        const message = await callMethod('mail.message.get', {
            id: messageId,
            select: ['id', 'subject', 'from', 'to', 'bindings', 'url']
        })
        
        console.log(message.item.bindings)
        
<?php
        
        $webhook = getenv('B24_HOOK');
        
        function callMethod(string $webhook, string $method, array $params)
        {
            $ch = curl_init($webhook . $method);
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
                CURLOPT_POSTFIELDS => json_encode($params, JSON_UNESCAPED_UNICODE),
                CURLOPT_RETURNTRANSFER => true,
            ]);
        
            $response = curl_exec($ch);
            if ($response === false)
            {
                throw new RuntimeException(curl_error($ch));
            }
        
            $data = json_decode($response, true);
            if (isset($data['error']))
            {
                throw new RuntimeException($data['error']['message']);
            }
        
            return $data['result'];
        }
        
        $mailboxes = callMethod($webhook, 'mail.mailbox.list', [
            'pagination' => ['page' => 1, 'limit' => 20, 'offset' => 0],
        ]);
        $mailbox = $mailboxes['items'][0] ?? null;
        if (!$mailbox)
        {
            throw new RuntimeException('No available mailboxes');
        }
        $mailboxId = $mailbox['id'];
        
        $messages = callMethod($webhook, 'mail.message.list', [
            'mailboxId' => $mailboxId,
            'searchQuery' => 'contract',
            'dateFrom' => '2026-08-01T00:00:00+02:00',
            'dateTo' => '2026-08-31T23:59:59+02:00',
            'pagination' => ['page' => 1, 'limit' => 20, 'offset' => 0],
        ]);
        $sourceMessage = $messages['items'][0] ?? null;
        if (!$sourceMessage)
        {
            throw new RuntimeException('E-mail not found');
        }
        $messageId = $sourceMessage['id'];
        
        callMethod($webhook, 'mail.message.createcrmactivity', [
            'messageId' => $messageId,
        ]);
        
        $message = callMethod($webhook, 'mail.message.get', [
            'id' => $messageId,
            'select' => ['id', 'subject', 'from', 'to', 'bindings', 'url'],
        ]);
        
        print_r($message['item']['bindings']);
        
import os
        
        from b24pysdk import BitrixWebhook, Client
        
        client = Client(
            BitrixWebhook(
                domain="your-domain.bitrix24.com",
                webhook_token=os.environ["B24_HOOK_TOKEN"],
            ),
            prefer_version=3,
        )
        
        mailboxes = client.mail.mailbox.list(
            pagination={"page": 1, "limit": 20, "offset": 0},
        ).response.result
        if not mailboxes["items"]:
            raise RuntimeError("No available mailboxes")
        
        mailbox_id = mailboxes["items"][0]["id"]
        
        messages = client.mail.message.list(
            mailbox_id=mailbox_id,
            search_query="contract",
            date_from="2026-08-01T00:00:00+02:00",
            date_to="2026-08-31T23:59:59+02:00",
            pagination={"page": 1, "limit": 20, "offset": 0},
        ).response.result
        if not messages["items"]:
            raise RuntimeError("E-mail not found")
        
        message_id = messages["items"][0]["id"]
        
        create_result = client.mail.message.createcrmactivity(
            message_id=message_id,
        ).response.result
        
        message = client.mail.message.get(
            bitrix_id=message_id,
            select=["id", "subject", "from", "to", "bindings", "url"],
        ).response.result
        
        print(message["item"]["bindings"])
        

Continue Learning