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
If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect to the MCP server so that the assistant can utilize 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 incoming 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"])