Документация для разработчиков

Даша.Роутер

Приём входящих писем: вы получаете адрес, письма на него разбираются и уходят на ваш обработчик обычным 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:

  1. Создайте в DNS своего домена MX-запись:
    inbound.your-app.ru.  IN  MX  10  mta.dashasender.ru.
  2. В личном кабинете, в разделе Аккаунт → Интеграции → Обработка входящих → Домены, добавьте поддомен и нажмите «Проверить» — мы сходим за MX-записью через DNS.
  3. После успешной проверки приём писем на этом поддомене включается.

Кириллические и другие национальные домены не поддерживаются.

Маршрут: фильтр #

Маршрут — это фильтр плюс действия. Письмо проходит по всем вашим маршрутам в порядке приоритета, от меньшего числа к большему; каждый совпавший фильтр запускает свои действия. Обход прекращается, когда встретится действие stop — как у Mailgun.

Фильтр задаётся JSON-объектом:

{
  "combinator": "and",
  "conditions": [
    {"field": "recipient", "op": "matches", "value": "support@.*"},
    {"field": "header", "name": "subject", "op": "matches", "value": "(?i)urgent"}
  ]
}
ПолеЗначение
combinatorand — должны совпасть все условия, or — хотя бы одно.
conditionsМассив условий. Пустой означает catch_all: маршрут срабатывает на любое письмо.
fieldrecipient, from, sender или header.
nameИмя заголовка, регистр не важен. Обязателен при field=header.
opmatches — регулярное выражение 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-htmlHTML-версия, если есть.
stripped-textТекст без процитированной переписки — только новая часть.
stripped-htmlТо же для HTML.
Message-IdЗаголовок Message-ID. По нему удобно отсекать повторы на своей стороне.
message-headersJSON-массив пар [[имя, значение], …] — все заголовки как есть, без раскодирования.
attachment-countСколько вложений.
attachmentsJSON-массив с описанием вложений.
spfРезультат проверки: pass, fail, neutral, softfail или пусто.
dkimpass, fail или пусто, если письмо не подписано.
dmarcРезультат проверки DMARC.
route-capturesJSON с захватами из регулярки совпавшего фильтра.
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, и запись в базе.

Доставка и ретраи #

Если обработчик не ответил или ответил ошибкой, мы пробуем ещё. Что считается чем:

ОтветЧто дальше
2xxdelivered. Всё, больше не трогаем.
4xxfailed, без повторов: адрес неверный, повторять бессмысленно.
5xx, таймаут, DNS, TLSretrying с растущими паузами.
ПопыткаПаузаС начала
10
21 минута~1 минута
35 минут~6 минут
430 минут~36 минут
52 часа~2,5 часа
финал6 часов~9 часов до failed

Подпись пересчитывается на каждой попытке, со свежими timestamp и token. Проверка свежести у вас пройдёт и через девять часов после первой попытки — специально закладывать запас не нужно.

Все попытки видны в личном кабинете (Аккаунт → Интеграции → Обработка входящих → Логи доставки) и через deliveriesList.

Управление доменами #

Системный поддомен выдан автоматически, кастомные — добавляются с MX-проверкой.

Список доменов #

GET ?method=router.domainsList

Список всех доменов аккаунта: системный (default) и кастомные. Default-домен выдан автоматически и всегда verified.

Что возвращается

Массив доменов аккаунта.

ПолеОписание
idидентификатор домена
domainполный поддомен
kindтип: default (системный) или custom (клиентский)
verify_statuspending / 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);
Ответ err_code 0
{
  "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);
Ответ err_code 0
{
  "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 обязательныйintid домена из 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);
Ответ err_code 0
{
  "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 обязательныйintid домена

Что возвращается

ПолеОписание
deletedtrue при успехе
idid удалённого домена
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);
Ответ err_code 0
{
  "response": {
    "msg": {
      "err_code": 0,
      "text": "OK",
      "type": "message"
    },
    "data": {
      "deleted": true,
      "id": 42
    }
  }
}

Параметры MX-записи #

GET ?method=router.mxInstructions

Параметры MX-записи для настройки DNS своего поддомена. Используется UI и сторонними клиентами для генерации инструкции.

Что возвращается

ПолеОписание
mx_targethostname 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);
Ответ err_code 0
{
  "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 ?method=router.routesList

Все маршруты аккаунта, отсортированные по приоритету (по возрастанию).

Что возвращается

ПолеОписание
idid маршрута
priorityприоритет (целое; меньше = выше в очереди)
descriptionпользовательское описание
filtercanonical JSON фильтра ({combinator, conditions[]})
expressionMailgun-совместимая строка фильтра
actionsмассив действий (webhook/store/forward/stop)
signing_keyHMAC-секрет (64 hex) для верификации подписи webhook
is_activetrue/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);
Ответ err_code 0
{
  "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 обязательныйintid маршрута

Что возвращается

Объект маршрута со структурой как в 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);
Ответ err_code 0
{
  "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.

Параметры

ПараметрТипОписание
descriptionstringописание маршрута, до 255 символов
expressionstringMailgun-совместимый фильтр одной строкой. Поддерживаемые функции: match_recipient(value), match_header(name,value), catch_all(). Объединение через and/or (один тип на выражение). Альтернатива — параметр filter
filterjsonфильтр в 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_activebool1 = маршрут активен (по умолчанию), 0 = выключен
priorityintцелое; маршруты обрабатываются по возрастанию. По умолчанию — следующее число после 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);
Ответ err_code 0
{
  "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 обязательныйintid маршрута
descriptionstringновое описание
expressionstringновый фильтр (Mailgun-строка)
filterjsonновый фильтр (canonical JSON)
actionsjsonновый массив действий
is_activeboolновое состояние
priorityintновый приоритет

Что возвращается

Объект обновлённого маршрута.

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);
Ответ err_code 0
{
  "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 обязательныйintid маршрута

Что возвращается

ПолеОписание
deletedtrue
idid удалённого маршрута
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);
Ответ err_code 0
{
  "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 обязательныйintid маршрута

Что возвращается

Объект маршрута с обновлённым 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);
Ответ err_code 0
{
  "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 обязательныйjsonJSON-массив 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);
Ответ err_code 0
{
  "response": {
    "msg": {
      "err_code": 0,
      "text": "OK",
      "type": "message"
    },
    "data": {
      "updated": 3
    }
  }
}

Хранилище сообщений #

Retrieve писем, сохранённых action store. TTL по умолчанию 3 дня.

Список сохранённых писем #

GET ?method=router.messagesList

Список сохранённых писем (от маршрутов с action store). Возвращает {items, total, limit, offset} для пагинации. Элементы — лёгкая мета (без body, без attachment-bytes); полная карточка — через messagesGet.

Параметры

ПараметрТипОписание
route_idintфильтр по маршруту
recipientstringLIKE-поиск по получателю
sincedatetimeот даты, формат YYYY-MM-DD HH:MM:SS
untildatetimeдо даты
limitintразмер страницы (1-200, по умолчанию 50)
offsetintсмещение для пагинации (по умолчанию 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);
Ответ err_code 0
{
  "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 обязательныйintid сообщения

Что возвращается

ПолеОписание
idid
route_idid маршрута
recipientполучатель
fromотправитель
subjectтема
message_idMessage-Id
body_textplain-текст письма
body_htmlHTML-версия (или null)
parsedparsed_json — снимок payload, как был бы отдан webhook
raw_eml_urlpresigned 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);
Ответ err_code 0
{
  "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 обязательныйintid сообщения

Что возвращается

ПолеОписание
deletedtrue
idid удалённого сообщения
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);
Ответ err_code 0
{
  "response": {
    "msg": {
      "err_code": 0,
      "text": "OK",
      "type": "message"
    },
    "data": {
      "deleted": true,
      "id": 9001
    }
  }
}

Ссылка на вложение #

GET ?method=router.messagesAttachment

Прямой presigned URL конкретного вложения. Удобен когда не нужна полная карточка письма — например, для скачивания файла по ссылке.

Параметры

ПараметрТипОписание
message_id обязательныйintid сообщения
attachment_id обязательныйintid вложения (из массива attachments в messagesGet)

Что возвращается

ПолеОписание
filenameимя файла
content_typeMIME-тип
sizeразмер в байтах
urlpresigned 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);
Ответ err_code 0
{
  "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_idintфильтр по маршруту
statusstringфильтр: pending / delivered / failed / retrying
recipientstringLIKE-поиск по получателю
sincedatetimeот даты
untildatetimeдо даты
limitint1-200, default 50
offsetintпагинация

Что возвращается

ПолеОписание
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);
Ответ err_code 0
{
  "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 обязательныйintid попытки доставки

Что возвращается

Объект попытки со структурой как в 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);
Ответ err_code 0
{
  "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 попытки доставки.

DashaMail хранит и защищает ваши данные на территории Российской Федерации

Подробнее...