How to Create a CRM Activity from an Incoming E-mail
Scope:
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
- mail.mailbox.list — any user
- mail.message.list — any user
- mail.message.createcrmactivity — a user with access to the mailbox where the e-mail is located and access to CRM
- mail.message.get — a user with access to the mailbox where the e-mail is located
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.
- Retrieve mailboxes using mail.mailbox.list
- Find an incoming e-mail using mail.message.list
- Create a CRM activity using mail.message.createcrmactivity
- 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
mailscope - 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 1searchQuery— e-mail search string. In the example, we search for e-mails by the wordcontractdateFromanddateTo— boundaries of the period where the e-mail must be foundpagination— 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
}
}
4. Check the E-mail Link
The mail.message.get method returns an e-mail by identifier.
Use the method with the parameters:
id— e-mail identifiermessageIdsaved from the mail.message.list response in step 2select— list of fields to retrieve. Request thebindingsfield 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 |
|
|
The webhook or application does not have the |
|
|
The user does not have access to the mailbox or e-mail. Check the webhook user |
|
|
An empty or invalid value was passed in |
|
|
The e-mail was not found. Check |
|
|
The e-mail search conditions did not pass validation. Check the |
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.createcrmactivitycreates a CRM activity from an existing e-mail and does not send a new e-mail- the
messageIdparameter of themail.message.createcrmactivitymethod is taken from the mail.message.list or mail.message.get response - the target CRM object cannot be passed as a parameter:
mail.message.createcrmactivityhas no fields for a lead, deal, contact, or company identifier - calling
mail.message.createcrmactivityagain for the same e-mail may return an error or leave the existing link unchanged; checkbindingsbefore 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"])