Даша.Роутер
Приём входящих писем: вы получаете адрес, письма на него разбираются и уходят на ваш обработчик обычным HTTP-запросом. Аналог Mailgun Inbound Routes и SendGrid Inbound Parse.
Что это #
Вам выдаётся адрес вида support@bold-gold-owl.route.dashamail.ru — или свой, help@inbound.your-app.ru. Дальше вы описываете правила: какое письмо куда отправить.
Что на этом обычно строят:
- Тикет-системы и поддержка. Клиент пишет на
help@your-app.ru, ваш бэкенд получает разобранное письмо и заводит тикет. - Email-to-CRM. Входящие лиды сразу попадают в воронку.
- Разбор служебной почты. Уведомления от платёжных систем и маркетплейсов обрабатываются программно.
- Команды письмом. Пользователь пишет на
note+task@your-app.ru, и письмо превращается в задачу.
Как обращаться к API #
Адрес тот же, что у остального API DashaMail, авторизация — параметром api_key. Все методы Роутера начинаются с router.:
https://api.dashamail.com/?method=router.routesList&api_key=YOUR_API_KEY
Работают и GET, и POST, кодировка UTF-8, формат ответа задаётся параметром format: JSON (по умолчанию), JSONP или XML.
На бесплатном тарифном плане API отключён по умолчанию. Напишите в техподдержку, и его откроют для вашего аккаунта.
Все методы есть в коллекции Postman — удобно попробовать, ничего не программируя.
Домены #
Системный поддомен выдаётся сразу и настройки не требует. Его имя — пара «прилагательное-цвет-животное», выведенная из вашего идентификатора: например, bold-gold-owl.route.dashamail.ru. Принимается любой адрес на этом поддомене: support@…, tasks@…, какой угодно.
Свой поддомен
Чтобы принимать письма на support@inbound.your-app.ru:
- Создайте в DNS своего домена MX-запись:
inbound.your-app.ru. IN MX 10 mta.dashasender.ru. - В личном кабинете, в разделе Аккаунт → Интеграции → Обработка входящих → Домены, добавьте поддомен и нажмите «Проверить» — мы сходим за MX-записью через DNS.
- После успешной проверки приём писем на этом поддомене включается.
Кириллические и другие национальные домены не поддерживаются.
Маршрут: фильтр #
Маршрут — это фильтр плюс действия. Письмо проходит по всем вашим маршрутам в порядке приоритета, от меньшего числа к большему; каждый совпавший фильтр запускает свои действия. Обход прекращается, когда встретится действие stop — как у Mailgun.
Фильтр задаётся JSON-объектом:
{
"combinator": "and",
"conditions": [
{"field": "recipient", "op": "matches", "value": "support@.*"},
{"field": "header", "name": "subject", "op": "matches", "value": "(?i)urgent"}
]
}
| Поле | Значение |
|---|---|
combinator | and — должны совпасть все условия, or — хотя бы одно. |
conditions | Массив условий. Пустой означает catch_all: маршрут срабатывает на любое письмо. |
field | recipient, from, sender или header. |
name | Имя заголовка, регистр не важен. Обязателен при field=header. |
op | matches — регулярное выражение PCRE, equals — точное совпадение, contains — подстрока. Два последних регистронезависимы. |
value | С чем сравнивать. Для matches — регулярка без ограничителей, в стиле Mailgun. |
Фильтр одной строкой #
Если привычнее писать фильтр как в Mailgun, передавайте expression вместо filter:
match_recipient("support@.*") and match_header("subject", "(?i)urgent")
Доступны match_recipient(value), match_header(name, value) и catch_all(). Объединяются через and или or — в одном выражении что-то одно.
Действия #
Массив объектов, у каждого обязательное поле type. На маршрут — не больше пяти действий.
[
{"type": "webhook", "url": "https://app.your-app.ru/inbound", "attachments": "url"},
{"type": "store", "ttl_days": 3},
{"type": "forward", "email": "manager@your-company.ru"},
{"type": "stop"}
]
| Тип | Поля | Что делает |
|---|---|---|
webhook |
url — обязательное, http или https, до 2048 байт. attachments — необязательное, см. раздел «Вложения». |
Отправляет разобранное письмо HTTP-запросом POST. |
store |
ttl_days — от 1 до 30, по умолчанию 3. notify_url — необязательное. |
Сохраняет письмо, чтобы забрать его потом через API. |
forward |
email — обязательное, не на нашем inbound-домене. |
Пересылает письмо на обычный адрес. |
stop |
— | Прерывает обход остальных маршрутов. |
Пересылка #
Действие forward отправляет входящее письмо на обычный адрес — аналог пересылки в Gmail. Заголовки при этом переписываются:
| Заголовок | Зачем |
|---|---|
From | Подменяется на "Имя via Dashamail" <fwd+id@…route.dashamail.ru> — так письмо проходит DMARC даже когда у исходного домена строгая политика. |
Reply-To | Исходный отправитель: ответ уйдёт ему, а не нам. |
Resent-* | Resent-From, Resent-Date, Resent-To, Resent-Message-ID — стандартная разметка пересылки по RFC. |
X-Loop | Вместе с Auto-Submitted: auto-forwarded защищает от петель. |
Пересылка работает только на платных тарифах — на бесплатном и триале маршрут с таким действием просто не создастся. Каждая успешная пересылка списывает с баланса одно транзакционное письмо, как обычная отправка через API.
Ограничения: 50 пересылок в час на маршрут и 1000 в сутки на аккаунт.
Статусы, которые появляются в логах доставки:
| Статус | Что случилось |
|---|---|
queued | Письмо передано в очередь отправки. |
failed_no_tariff | Тариф не позволяет пересылку. |
failed_no_balance | Баланс отрицательный. |
failed_rate_limited | Превышен лимит пересылок. |
failed_loop | Попытка переслать на наш же inbound-домен. |
failed_publish | Внутренняя ошибка очереди. |
При любом failed_*, который можно исправить — тариф, баланс, очередь — владельцу маршрута уходит письмо. Не чаще одного в час на маршрут.
Дальнейшая судьба письма у получателя видна только по возвратам: саму SMTP-доставку выполняет MTA, и в логах Роутера её статуса нет.
Что приходит в webhook #
Запрос приходит методом POST с Content-Type: application/x-www-form-urlencoded.
| Поле | Описание |
|---|---|
recipient | Получатель из конверта SMTP (RCPT TO). |
sender | Отправитель из конверта. |
from | Заголовок From целиком, раскодированный из MIME в UTF-8. |
subject | Тема, тоже раскодированная. |
body-plain | Текстовое тело. Если в письме его не было — собранное из HTML. |
body-html | HTML-версия, если есть. |
stripped-text | Текст без процитированной переписки — только новая часть. |
stripped-html | То же для HTML. |
Message-Id | Заголовок Message-ID. По нему удобно отсекать повторы на своей стороне. |
message-headers | JSON-массив пар [[имя, значение], …] — все заголовки как есть, без раскодирования. |
attachment-count | Сколько вложений. |
attachments | JSON-массив с описанием вложений. |
spf | Результат проверки: pass, fail, neutral, softfail или пусто. |
dkim | pass, fail или пусто, если письмо не подписано. |
dmarc | Результат проверки DMARC. |
route-captures | JSON с захватами из регулярки совпавшего фильтра. |
timestamp | Время отправки, unix-секунды. |
token | Случайная hex-строка на 50 символов, своя у каждой попытки. |
signature | Подпись — см. следующий раздел. |
Проверка подписи #
Запрос на ваш обработчик может отправить кто угодно, поэтому каждый мы подписываем:
signature = HMAC_SHA256(signing_key, timestamp + token)
signing_key — секрет конкретного маршрута. Его видно при редактировании маршрута в личном кабинете, и его можно перевыпустить методом routesRegenerateKey.
На своей стороне нужно склеить timestamp и token, посчитать от них HMAC-SHA256 с этим ключом и сравнить с пришедшей подписью — обязательно сравнением за постоянное время, иначе секрет утечёт по таймингам. И отдельно проверить, что timestamp свежий: это защита от повторной отправки перехваченного запроса.
# Посчитать ожидаемую подпись в консоли — удобно для разовой проверки
# значений, которые вы уже видите в логе своего обработчика.
SIGNING_KEY='your_route_signing_key'
TIMESTAMP='1787846400'
TOKEN='b1946ac92492d2347c6235b4d2611184'
printf '%s' "$TIMESTAMP$TOKEN" \
| openssl dgst -sha256 -hmac "$SIGNING_KEY" -r \
| cut -d' ' -f1
# Результат должен совпасть с полем signature из запроса.
<?php
$signing_key = 'your_route_signing_key';
$timestamp = isset($_POST['timestamp']) ? $_POST['timestamp'] : '';
$token = isset($_POST['token']) ? $_POST['token'] : '';
$signature = isset($_POST['signature']) ? $_POST['signature'] : '';
// 1. Свежесть — защита от повторной отправки перехваченного запроса
if (abs(time() - (int)$timestamp) > 600) {
http_response_code(403);
exit('stale timestamp');
}
// 2. Ожидаемая подпись
$expected = hash_hmac('sha256', $timestamp . $token, $signing_key);
// 3. Сравнение за постоянное время
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit('signature mismatch');
}
// Подпись верна — можно работать с письмом
$from = $_POST['from'];
$subject = $_POST['subject'];
$body_text = $_POST['body-plain'];
http_response_code(200);
echo 'OK';
import hmac, hashlib, time
from flask import Flask, request, abort
app = Flask(__name__)
SIGNING_KEY = b'your_route_signing_key'
@app.route('/inbound', methods=['POST'])
def inbound():
ts = request.form.get('timestamp', '')
tok = request.form.get('token', '')
sig = request.form.get('signature', '')
# 1. Свежесть
if abs(time.time() - int(ts)) > 600:
abort(403, 'stale timestamp')
# 2. Ожидаемая подпись
expected = hmac.new(SIGNING_KEY, (ts + tok).encode(), hashlib.sha256).hexdigest()
# 3. Сравнение за постоянное время
if not hmac.compare_digest(expected, sig):
abort(403, 'signature mismatch')
from_ = request.form['from']
subject = request.form['subject']
body = request.form['body-plain']
return 'OK', 200
const express = require('express');
const crypto = require('crypto');
const app = express();
// 50mb — с запасом на вложения в режиме inline
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
const SIGNING_KEY = 'your_route_signing_key';
app.post('/inbound', (req, res) => {
const { timestamp, token, signature } = req.body;
// 1. Свежесть
if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 600) {
return res.status(403).send('stale timestamp');
}
// 2. Ожидаемая подпись
const expected = crypto
.createHmac('sha256', SIGNING_KEY)
.update(timestamp + token)
.digest('hex');
// 3. Сравнение за постоянное время
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(signature || '', 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(403).send('signature mismatch');
}
const { from, subject } = req.body;
const bodyText = req.body['body-plain'];
res.status(200).send('OK');
});
app.listen(3000);
Вложения #
Поле attachments в действии webhook задаёт, в каком виде приходят файлы. По умолчанию — off.
| Режим | Что в запросе | Когда брать |
|---|---|---|
off |
Только описание: filename, content_type, size, content_id. |
Содержимое не нужно — достаточно знать, что файлы были. |
url |
Плюс поле url со ссылкой на скачивание. Ссылка живёт 24 часа. |
Скачивать по необходимости. Запрос остаётся лёгким. |
inline |
Плюс поле content_b64 с содержимым в base64. |
Обработчик не умеет ходить за файлами сам, или файл нужен сразу — антивирус, конвертация. |
attachments=[
{
"filename": "invoice.pdf",
"content_type": "application/pdf",
"size": 12345,
"content_id": null,
"content_b64": "JVBERi0xLjQKJ..."
}
]
У режима inline есть потолок: суммарно не больше 25 MiB вложений на письмо, а после base64 тело запроса вырастает примерно до 34 МБ. Что не поместилось, остаётся в массиве с пометкой truncated: true.
Проверьте, что ваш сервер принимает такие запросы — у nginx это client_max_body_size.
Захваты из фильтра #
В условиях с matches работают группы захвата PCRE — и нумерованные (…), и именованные (?P<name>…). Захваченное приходит в поле route-captures и, что интереснее, подставляется прямо в адрес обработчика через \1, \2, \g<name>.
Скажем, фильтр match_recipient("(?P<mailbox>[^@]+)@(?P<domain>.+)") и адрес обработчика https://app.your.ru/inbox/\g<mailbox>. Тогда письмо на support@bold-gold-owl.route.dashamail.ru уйдёт на https://app.your.ru/inbox/support, а в запросе будет:
route-captures={"mailbox":"support","domain":"bold-gold-owl.route.dashamail.ru"}
Хранилище #
Действие store сохраняет письмо на ttl_days дней — по умолчанию три, можно от одного до тридцати. Забрать его потом можно четырьмя методами:
- messagesList — список с фильтрами по маршруту, получателю и дате.
- messagesGet — полная карточка со ссылками на сырое письмо и вложения.
- messagesAttachment — ссылка на одно вложение.
- messagesDelete — удалить, не дожидаясь конца срока.
Ссылки на файлы живут час и выписываются заново при каждом вызове — сохранять их у себя смысла нет. По истечении срока письмо удаляется целиком: и вложения, и сырой EML, и запись в базе.
Доставка и ретраи #
Если обработчик не ответил или ответил ошибкой, мы пробуем ещё. Что считается чем:
| Ответ | Что дальше |
|---|---|
2xx | delivered. Всё, больше не трогаем. |
4xx | failed, без повторов: адрес неверный, повторять бессмысленно. |
5xx, таймаут, DNS, TLS | retrying с растущими паузами. |
| Попытка | Пауза | С начала |
|---|---|---|
| 1 | — | 0 |
| 2 | 1 минута | ~1 минута |
| 3 | 5 минут | ~6 минут |
| 4 | 30 минут | ~36 минут |
| 5 | 2 часа | ~2,5 часа |
| финал | 6 часов | ~9 часов до failed |
Подпись пересчитывается на каждой попытке, со свежими timestamp и token. Проверка свежести у вас пройдёт и через девять часов после первой попытки — специально закладывать запас не нужно.
Все попытки видны в личном кабинете (Аккаунт → Интеграции → Обработка входящих → Логи доставки) и через deliveriesList.
Управление доменами #
Системный поддомен выдан автоматически, кастомные — добавляются с MX-проверкой.
- GET Список доменов
- POST Добавить свой поддомен
- POST Проверить MX-запись
- POST Удалить поддомен
- GET Параметры MX-записи
Список доменов #
GET
?method=router.domainsList
Список всех доменов аккаунта: системный (default) и кастомные. Default-домен выдан автоматически и всегда verified.
Что возвращается
Массив доменов аккаунта.
| Поле | Описание |
|---|---|
id | идентификатор домена |
domain | полный поддомен |
kind | тип: default (системный) или custom (клиентский) |
verify_status | pending / verified / failed |
verified_at | дата и время последней успешной проверки |
created_at | дата создания |
curl 'https://api.dashamail.com/?method=router.domainsList&api_key=YOUR_API_KEY'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.domainsList',
'api_key' => 'YOUR_API_KEY',
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.domainsList',
'api_key': 'YOUR_API_KEY',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.domainsList",
"api_key": "YOUR_API_KEY"
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": [
{
"id": 1,
"domain": "bold-gold-owl.route.dashamail.ru",
"kind": "default",
"verify_status": "verified",
"verified_at": "2026-08-01 10:12:00",
"created_at": "2026-08-01 10:12:00"
},
{
"id": 42,
"domain": "inbound.your-app.ru",
"kind": "custom",
"verify_status": "pending",
"verified_at": null,
"created_at": "2026-08-27 14:03:11"
}
]
}
}
Добавить свой поддомен #
POST
?method=router.domainsCreate
Добавить кастомный поддомен. После создания verify_status=pending — нужно настроить MX-запись на mta.dashasender.ru и вызвать domainsVerify.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
domain обязательный | string | клиентский поддомен (только ASCII, минимум один уровень, без trailing dot). Например, inbound.your-domain.com |
Что возвращается
Объект созданного домена со структурой как в domainsList.
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.domainsCreate",
"api_key": "YOUR_API_KEY",
"domain": "inbound.your-app.ru"
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.domainsCreate',
'api_key' => 'YOUR_API_KEY',
'domain' => 'inbound.your-app.ru',
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.domainsCreate',
'api_key': 'YOUR_API_KEY',
'domain': 'inbound.your-app.ru',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.domainsCreate",
"api_key": "YOUR_API_KEY",
"domain": "inbound.your-app.ru"
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 42,
"domain": "inbound.your-app.ru",
"kind": "custom",
"verify_status": "pending",
"verified_at": null,
"created_at": "2026-08-27 14:03:11"
}
}
}
Проверить MX-запись #
POST
?method=router.domainsVerify
DNS-проверка MX-записи кастомного домена. При успехе verify_status переходит в verified и домен автоматически регистрируется в Даше.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
domain_id обязательный | int | id домена из domainsList |
Что возвращается
Объект домена с обновлённым verify_status.
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.domainsVerify",
"api_key": "YOUR_API_KEY",
"domain_id": 42
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.domainsVerify',
'api_key' => 'YOUR_API_KEY',
'domain_id' => 42,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.domainsVerify',
'api_key': 'YOUR_API_KEY',
'domain_id': 42,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.domainsVerify",
"api_key": "YOUR_API_KEY",
"domain_id": 42
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 42,
"domain": "inbound.your-app.ru",
"kind": "custom",
"verify_status": "verified",
"verified_at": "2026-08-27 14:20:05"
}
}
}
Удалить поддомен #
POST
?method=router.domainsDelete
Удалить кастомный поддомен. Default-домен удалить нельзя. Маршруты, привязанные к домену, не удаляются — но письма на этот домен перестают приниматься.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
domain_id обязательный | int | id домена |
Что возвращается
| Поле | Описание |
|---|---|
deleted | true при успехе |
id | id удалённого домена |
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.domainsDelete",
"api_key": "YOUR_API_KEY",
"domain_id": 42
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.domainsDelete',
'api_key' => 'YOUR_API_KEY',
'domain_id' => 42,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.domainsDelete',
'api_key': 'YOUR_API_KEY',
'domain_id': 42,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.domainsDelete",
"api_key": "YOUR_API_KEY",
"domain_id": 42
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"deleted": true,
"id": 42
}
}
}
Параметры MX-записи #
GET
?method=router.mxInstructions
Параметры MX-записи для настройки DNS своего поддомена. Используется UI и сторонними клиентами для генерации инструкции.
Что возвращается
| Поле | Описание |
|---|---|
mx_target | hostname MTA — куда настроить MX-запись |
priority | рекомендуемый приоритет MX (10) |
example | объект с примером DNS-записи: host, type=MX, priority, value, ttl |
note | подсказка для клиента про пропагацию DNS |
curl 'https://api.dashamail.com/?method=router.mxInstructions&api_key=YOUR_API_KEY'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.mxInstructions',
'api_key' => 'YOUR_API_KEY',
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.mxInstructions',
'api_key': 'YOUR_API_KEY',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.mxInstructions",
"api_key": "YOUR_API_KEY"
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"mx_target": "mta.dashasender.ru",
"priority": 10,
"example": {
"host": "inbound.your-app.ru",
"type": "MX",
"priority": 10,
"value": "mta.dashasender.ru",
"ttl": 3600
},
"note": "Изменения DNS расходятся до нескольких часов."
}
}
}
Маршруты #
CRUD маршрутов с фильтрами и действиями. Mailgun-совместимая семантика.
- GET Список маршрутов
- GET Один маршрут
- POST Создать маршрут
- POST Изменить маршрут
- POST Удалить маршрут
- POST Перевыпустить ключ подписи
- POST Переупорядочить маршруты
Список маршрутов #
GET
?method=router.routesList
Все маршруты аккаунта, отсортированные по приоритету (по возрастанию).
Что возвращается
| Поле | Описание |
|---|---|
id | id маршрута |
priority | приоритет (целое; меньше = выше в очереди) |
description | пользовательское описание |
filter | canonical JSON фильтра ({combinator, conditions[]}) |
expression | Mailgun-совместимая строка фильтра |
actions | массив действий (webhook/store/forward/stop) |
signing_key | HMAC-секрет (64 hex) для верификации подписи webhook |
is_active | true/false |
match_count | счётчик матчей маршрута |
last_matched_at | время последнего матча |
created_at | дата создания |
updated_at | дата последнего обновления |
curl 'https://api.dashamail.com/?method=router.routesList&api_key=YOUR_API_KEY'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.routesList',
'api_key' => 'YOUR_API_KEY',
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.routesList',
'api_key': 'YOUR_API_KEY',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.routesList",
"api_key": "YOUR_API_KEY"
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": [
{
"id": 7,
"priority": 0,
"description": "Обращения в поддержку",
"expression": "match_recipient(\"support@.*\")",
"actions": [
{
"type": "webhook",
"url": "https://app.your-app.ru/inbound",
"attachments": "url"
}
],
"signing_key": "9f1c…64 hex",
"is_active": true,
"match_count": 1284,
"last_matched_at": "2026-08-27 13:58:20",
"created_at": "2026-08-01 11:00:00",
"updated_at": "2026-08-20 09:14:33"
}
]
}
}
Один маршрут #
GET
?method=router.routesGet
Получить один маршрут по id (включая signing_key).
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id обязательный | int | id маршрута |
Что возвращается
Объект маршрута со структурой как в routesList.
curl 'https://api.dashamail.com/?method=router.routesGet&api_key=YOUR_API_KEY&route_id=7'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.routesGet',
'api_key' => 'YOUR_API_KEY',
'route_id' => 7,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.routesGet',
'api_key': 'YOUR_API_KEY',
'route_id': 7,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.routesGet",
"api_key": "YOUR_API_KEY",
"route_id": 7
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 7,
"priority": 0,
"description": "Обращения в поддержку",
"signing_key": "9f1c…64 hex",
"is_active": true
}
}
}
Создать маршрут #
POST
?method=router.routesCreate
Создать маршрут. Возвращает объект созданного маршрута с автоматически сгенерированным signing_key.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
description | string | описание маршрута, до 255 символов |
expression | string | Mailgun-совместимый фильтр одной строкой. Поддерживаемые функции: match_recipient(value), match_header(name,value), catch_all(). Объединение через and/or (один тип на выражение). Альтернатива — параметр filter |
filter | json | фильтр в canonical-JSON формате: {"combinator":"and|or","conditions":[{field,op,value,name?}]}. Поля: recipient/from/sender/header. Ops: matches (PCRE)/equals/contains. Пустой conditions — catch_all |
actions обязательный | json | массив действий, максимум 5. Типы: webhook (url + опц. attachments: off/url/inline), store (опц. ttl_days 1-30 и notify_url), forward (email — пересылка письма, только на платных тарифах, списывает 1 транзакционное письмо с баланса), stop |
is_active | bool | 1 = маршрут активен (по умолчанию), 0 = выключен |
priority | int | целое; маршруты обрабатываются по возрастанию. По умолчанию — следующее число после max существующего |
Что возвращается
Объект созданного маршрута.
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.routesCreate",
"api_key": "YOUR_API_KEY",
"description": "Обращения в поддержку",
"expression": "match_recipient(\"support@.*\")",
"actions": "[{\"type\":\"webhook\",\"url\":\"https://app.your-app.ru/inbound\",\"attachments\":\"url\"},{\"type\":\"store\",\"ttl_days\":7}]"
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.routesCreate',
'api_key' => 'YOUR_API_KEY',
'description' => 'Обращения в поддержку',
'expression' => 'match_recipient("support@.*")',
'actions' => '[{"type":"webhook","url":"https://app.your-app.ru/inbound","attachments":"url"},{"type":"store","ttl_days":7}]',
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.routesCreate',
'api_key': 'YOUR_API_KEY',
'description': 'Обращения в поддержку',
'expression': 'match_recipient("support@.*")',
'actions': '[{"type":"webhook","url":"https://app.your-app.ru/inbound","attachments":"url"},{"type":"store","ttl_days":7}]',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.routesCreate",
"api_key": "YOUR_API_KEY",
"description": "Обращения в поддержку",
"expression": "match_recipient(\"support@.*\")",
"actions": "[{\"type\":\"webhook\",\"url\":\"https://app.your-app.ru/inbound\",\"attachments\":\"url\"},{\"type\":\"store\",\"ttl_days\":7}]"
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 7,
"priority": 0,
"signing_key": "9f1c…64 hex",
"is_active": true,
"created_at": "2026-08-27 14:31:02"
}
}
}
Изменить маршрут #
POST
?method=router.routesUpdate
Partial-update маршрута. Обновляются только переданные поля.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id обязательный | int | id маршрута |
description | string | новое описание |
expression | string | новый фильтр (Mailgun-строка) |
filter | json | новый фильтр (canonical JSON) |
actions | json | новый массив действий |
is_active | bool | новое состояние |
priority | int | новый приоритет |
Что возвращается
Объект обновлённого маршрута.
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.routesUpdate",
"api_key": "YOUR_API_KEY",
"route_id": 7,
"is_active": 0
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.routesUpdate',
'api_key' => 'YOUR_API_KEY',
'route_id' => 7,
'is_active' => 0,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.routesUpdate',
'api_key': 'YOUR_API_KEY',
'route_id': 7,
'is_active': 0,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.routesUpdate",
"api_key": "YOUR_API_KEY",
"route_id": 7,
"is_active": 0
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 7,
"is_active": false,
"updated_at": "2026-08-27 14:35:41"
}
}
}
Удалить маршрут #
POST
?method=router.routesDelete
Удалить маршрут. Логи доставки (InboundDeliveries) с этого маршрута остаются для аудита.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id обязательный | int | id маршрута |
Что возвращается
| Поле | Описание |
|---|---|
deleted | true |
id | id удалённого маршрута |
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.routesDelete",
"api_key": "YOUR_API_KEY",
"route_id": 7
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.routesDelete',
'api_key' => 'YOUR_API_KEY',
'route_id' => 7,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.routesDelete',
'api_key': 'YOUR_API_KEY',
'route_id': 7,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.routesDelete",
"api_key": "YOUR_API_KEY",
"route_id": 7
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"deleted": true,
"id": 7
}
}
}
Перевыпустить ключ подписи #
POST
?method=router.routesRegenerateKey
Сгенерировать новый signing_key (при компроментации). In-flight доставки, опубликованные ранее, остаются со снапшотом старого ключа — клиентский endpoint должен их пропустить через старый секрет.
Доставки, опубликованные до перевыпуска, подписаны старым ключом. Держите его действующим ещё несколько часов — иначе ретраи не пройдут проверку подписи.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id обязательный | int | id маршрута |
Что возвращается
Объект маршрута с обновлённым signing_key.
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.routesRegenerateKey",
"api_key": "YOUR_API_KEY",
"route_id": 7
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.routesRegenerateKey',
'api_key' => 'YOUR_API_KEY',
'route_id' => 7,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.routesRegenerateKey',
'api_key': 'YOUR_API_KEY',
'route_id': 7,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.routesRegenerateKey",
"api_key": "YOUR_API_KEY",
"route_id": 7
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 7,
"signing_key": "3ab7…новый 64 hex"
}
}
}
Переупорядочить маршруты #
POST
?method=router.routesReorder
Bulk-обновление приоритетов нескольких маршрутов (для drag-and-drop в UI). Маршрут на позиции i получает priority = i.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
order обязательный | json | JSON-массив id маршрутов в желаемом порядке. Например, [3,1,2] |
Что возвращается
| Поле | Описание |
|---|---|
updated | число обновлённых маршрутов |
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.routesReorder",
"api_key": "YOUR_API_KEY",
"order": "[3,1,2]"
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.routesReorder',
'api_key' => 'YOUR_API_KEY',
'order' => '[3,1,2]',
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.routesReorder',
'api_key': 'YOUR_API_KEY',
'order': '[3,1,2]',
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.routesReorder",
"api_key": "YOUR_API_KEY",
"order": "[3,1,2]"
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"updated": 3
}
}
}
Хранилище сообщений #
Retrieve писем, сохранённых action store. TTL по умолчанию 3 дня.
- GET Список сохранённых писем
- GET Карточка письма
- POST Удалить письмо
- GET Ссылка на вложение
Список сохранённых писем #
GET
?method=router.messagesList
Список сохранённых писем (от маршрутов с action store). Возвращает {items, total, limit, offset} для пагинации. Элементы — лёгкая мета (без body, без attachment-bytes); полная карточка — через messagesGet.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id | int | фильтр по маршруту |
recipient | string | LIKE-поиск по получателю |
since | datetime | от даты, формат YYYY-MM-DD HH:MM:SS |
until | datetime | до даты |
limit | int | размер страницы (1-200, по умолчанию 50) |
offset | int | смещение для пагинации (по умолчанию 0) |
Что возвращается
| Поле | Описание |
|---|---|
items | массив сообщенийid — id сообщения;route_id — id маршрута, который сохранил;recipient — SMTP RCPT TO;from — email отправителя;subject — тема, декодированная из MIME;message_id — заголовок Message-Id;attachments_count — число вложений;raw_size — размер сырого EML в байтах;expires_at — время автоматического удаления;retrieved_at — время первого retrieve через API (или null);created_at — время приёма письма |
total | общее количество с учётом фильтров |
limit | применённый лимит |
offset | применённое смещение |
curl 'https://api.dashamail.com/?method=router.messagesList&api_key=YOUR_API_KEY&limit=2'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.messagesList',
'api_key' => 'YOUR_API_KEY',
'limit' => 2,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.messagesList',
'api_key': 'YOUR_API_KEY',
'limit': 2,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.messagesList",
"api_key": "YOUR_API_KEY",
"limit": 2
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"items": [
{
"id": 9001,
"route_id": 7,
"recipient": "support@bold-gold-owl.route.dashamail.ru",
"from": "ivan@example.com",
"subject": "Не приходит счёт",
"message_id": "<a1b2@example.com>",
"attachments_count": 1,
"raw_size": 48211,
"expires_at": "2026-08-30 13:58:20",
"retrieved_at": null,
"created_at": "2026-08-27 13:58:20"
}
],
"total": 128,
"limit": 2,
"offset": 0
}
}
}
Карточка письма #
GET
?method=router.messagesGet
Полная карточка сообщения. Включает свежие presigned-ссылки (TTL 1 час) на raw EML и каждое вложение — регенерируются на каждый вызов, не храним. На первом обращении помечает retrieved_at.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
message_id обязательный | int | id сообщения |
Что возвращается
| Поле | Описание |
|---|---|
id | id |
route_id | id маршрута |
recipient | получатель |
from | отправитель |
subject | тема |
message_id | Message-Id |
body_text | plain-текст письма |
body_html | HTML-версия (или null) |
parsed | parsed_json — снимок payload, как был бы отдан webhook |
raw_eml_url | presigned URL для скачивания сырого EML (TTL 1ч) |
raw_size | размер EML в байтах |
attachments | массив вложенийid — id вложения;filename — имя файла;content_type — MIME-тип;size — размер в байтах;content_id — Content-ID для inline-картинок (или null);url — presigned URL для скачивания (TTL 1ч) |
expires_at | время TTL-удаления |
retrieved_at | время первого retrieve |
created_at | время приёма |
curl 'https://api.dashamail.com/?method=router.messagesGet&api_key=YOUR_API_KEY&message_id=9001'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.messagesGet',
'api_key' => 'YOUR_API_KEY',
'message_id' => 9001,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.messagesGet',
'api_key': 'YOUR_API_KEY',
'message_id': 9001,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.messagesGet",
"api_key": "YOUR_API_KEY",
"message_id": 9001
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 9001,
"route_id": 7,
"recipient": "support@bold-gold-owl.route.dashamail.ru",
"from": "ivan@example.com",
"subject": "Не приходит счёт",
"body_text": "Здравствуйте! Счёт за август так и не пришёл.",
"body_html": null,
"raw_eml_url": "https://storage.dashamail.ru/inbound/…?X-Amz-Expires=3600",
"raw_size": 48211,
"attachments": [
{
"id": 5,
"filename": "screenshot.png",
"content_type": "image/png",
"size": 31044,
"content_id": null,
"url": "https://storage.dashamail.ru/inbound/…?X-Amz-Expires=3600"
}
],
"expires_at": "2026-08-30 13:58:20",
"retrieved_at": "2026-08-27 15:02:10",
"created_at": "2026-08-27 13:58:20"
}
}
}
Удалить письмо #
POST
?method=router.messagesDelete
Удалить сохранённое сообщение раньше TTL. Снимает raw EML и вложения из S3, удаляет строки БД.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
message_id обязательный | int | id сообщения |
Что возвращается
| Поле | Описание |
|---|---|
deleted | true |
id | id удалённого сообщения |
curl -X POST 'https://api.dashamail.com/' \
-H 'Content-Type: application/json' \
-d '{
"method": "router.messagesDelete",
"api_key": "YOUR_API_KEY",
"message_id": 9001
}'
<?php
$ch = curl_init('https://api.dashamail.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'method' => 'router.messagesDelete',
'api_key' => 'YOUR_API_KEY',
'message_id' => 9001,
], JSON_UNESCAPED_UNICODE),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.post(
'https://api.dashamail.com/',
json={
'method': 'router.messagesDelete',
'api_key': 'YOUR_API_KEY',
'message_id': 9001,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const response = await fetch('https://api.dashamail.com/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"method": "router.messagesDelete",
"api_key": "YOUR_API_KEY",
"message_id": 9001
}),
});
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"deleted": true,
"id": 9001
}
}
}
Ссылка на вложение #
GET
?method=router.messagesAttachment
Прямой presigned URL конкретного вложения. Удобен когда не нужна полная карточка письма — например, для скачивания файла по ссылке.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
message_id обязательный | int | id сообщения |
attachment_id обязательный | int | id вложения (из массива attachments в messagesGet) |
Что возвращается
| Поле | Описание |
|---|---|
filename | имя файла |
content_type | MIME-тип |
size | размер в байтах |
url | presigned URL (TTL 1ч) |
curl 'https://api.dashamail.com/?method=router.messagesAttachment&api_key=YOUR_API_KEY&message_id=9001&attachment_id=5'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.messagesAttachment',
'api_key' => 'YOUR_API_KEY',
'message_id' => 9001,
'attachment_id' => 5,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.messagesAttachment',
'api_key': 'YOUR_API_KEY',
'message_id': 9001,
'attachment_id': 5,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.messagesAttachment",
"api_key": "YOUR_API_KEY",
"message_id": 9001,
"attachment_id": 5
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"filename": "screenshot.png",
"content_type": "image/png",
"size": 31044,
"url": "https://storage.dashamail.ru/inbound/…?X-Amz-Expires=3600"
}
}
}
Логи доставки #
Лог попыток доставки webhook'ов — статусы, ретраи, response_excerpt.
Лог попыток доставки #
GET
?method=router.deliveriesList
История попыток доставки webhook'ов. Без чувствительных полей (signing_key, payload_json) — только метаданные для отладки. Возвращает {items, total, limit, offset}.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
route_id | int | фильтр по маршруту |
status | string | фильтр: pending / delivered / failed / retrying |
recipient | string | LIKE-поиск по получателю |
since | datetime | от даты |
until | datetime | до даты |
limit | int | 1-200, default 50 |
offset | int | пагинация |
Что возвращается
| Поле | Описание |
|---|---|
items | массив попыток доставкиid — id попытки;route_id — id маршрута, который триггерил доставку;target_url — URL клиентского endpoint;recipient — получатель письма;message_id — Message-Id входящего письма;attempt — номер текущей попытки (1-based);http_status — HTTP-код ответа (или null если transport-error);response_excerpt — первые ~1KB ответа клиента;status — pending / delivered / failed / retrying;next_retry_at — время следующей попытки (или null);created_at — время первой попытки;updated_at — время последней попытки |
total | общее количество с учётом фильтров |
limit | применённый лимит |
offset | применённое смещение |
curl 'https://api.dashamail.com/?method=router.deliveriesList&api_key=YOUR_API_KEY&status=failed&limit=2'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.deliveriesList',
'api_key' => 'YOUR_API_KEY',
'status' => 'failed',
'limit' => 2,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.deliveriesList',
'api_key': 'YOUR_API_KEY',
'status': 'failed',
'limit': 2,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.deliveriesList",
"api_key": "YOUR_API_KEY",
"status": "failed",
"limit": 2
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"items": [
{
"id": 5512,
"route_id": 7,
"target_url": "https://app.your-app.ru/inbound",
"recipient": "support@bold-gold-owl.route.dashamail.ru",
"message_id": "<a1b2@example.com>",
"attempt": 6,
"http_status": 500,
"response_excerpt": "Internal Server Error",
"status": "failed",
"next_retry_at": null,
"created_at": "2026-08-27 04:10:00",
"updated_at": "2026-08-27 13:10:00"
}
],
"total": 3,
"limit": 2,
"offset": 0
}
}
}
Одна попытка доставки #
GET
?method=router.deliveriesGet
Детали одной попытки доставки. Используется для отладки 5xx-ответов клиентского endpoint.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
delivery_id обязательный | int | id попытки доставки |
Что возвращается
Объект попытки со структурой как в deliveriesList.items.
curl 'https://api.dashamail.com/?method=router.deliveriesGet&api_key=YOUR_API_KEY&delivery_id=5512'
<?php
$url = 'https://api.dashamail.com/?' . http_build_query([
'method' => 'router.deliveriesGet',
'api_key' => 'YOUR_API_KEY',
'delivery_id' => 5512,
]);
$response = json_decode(file_get_contents($url), true);
if ($response['response']['msg']['err_code'] !== 0) {
throw new RuntimeException($response['response']['msg']['text']);
}
print_r($response['response']['data']);
import requests
response = requests.get(
'https://api.dashamail.com/',
params={
'method': 'router.deliveriesGet',
'api_key': 'YOUR_API_KEY',
'delivery_id': 5512,
},
)
payload = response.json()['response']
if payload['msg']['err_code'] != 0:
raise RuntimeError(payload['msg']['text'])
print(payload['data'])
const params = new URLSearchParams({
"method": "router.deliveriesGet",
"api_key": "YOUR_API_KEY",
"delivery_id": 5512
});
const response = await fetch(`https://api.dashamail.com/?${params}`);
const { response: payload } = await response.json();
if (payload.msg.err_code !== 0) throw new Error(payload.msg.text);
console.log(payload.data);
{
"response": {
"msg": {
"err_code": 0,
"text": "OK",
"type": "message"
},
"data": {
"id": 5512,
"route_id": 7,
"attempt": 6,
"http_status": 500,
"response_excerpt": "Internal Server Error",
"status": "failed"
}
}
}
Коды ошибок #
В дополнение к общим кодам:
err_code | Когда возникает |
|---|---|
2 | Раздел недоступен, ошибка базы или недостаточно прав. |
3 | Не прошла проверка данных — некорректный адрес обработчика, регулярка или формат фильтра. |
4 | Маршрут, сообщение или доставка не найдены. |
7 | Домен уже добавлен в этот аккаунт. |
54 | Некорректный домен: только ASCII, минимум один уровень. |
55 | Домен зарезервирован системой — попытка занять *.route.dashamail.ru. |
56 | Домен занят другим аккаунтом. |
Вопросы #
Метод ведёт себя не так, как здесь написано, или письмо не дошло до обработчика — пишите на support@dashamail.ru. Приложите Message-Id письма или id попытки доставки.