Открыть dashboard

API v1 · Public beta

Создайте временный inbox, отправьте на него письмо и получите результат через REST API. Все запросы и ответы используют JSON, кроме скачивания MIME и вложений.

Beta сохраняет URL и контракт alpha. Опциональные поля и методы могут добавляться в v1; breaking changes требуют новой major-версии API или пакета.

Base URLhttps://api.tstly.ru

Быстрый старт

1. Выпустите API key

Войдите в dashboard, откройте «Ключи API» и сохраните secret: он показывается только один раз.

2. Создайте inbox

Сгенерируйте ключ операции один раз: export IDEMPOTENCY_KEY="$(uuidgen)". При сетевом retry повторяйте запрос с тем же ключом и тем же body.

curl -X POST https://api.tstly.ru/v1/inboxes \
  -H "Authorization: Bearer $TSTLY_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"localPart":"signup","retentionSeconds":86400}'
201 Created
{
  "id": "019…",
  "tenantId": "019…",
  "localPart": "signup",
  "address": "signup@acme.tstly.ru",
  "retentionSeconds": 86400,
  "createdAt": "2026-08-01T12:00:00Z"
}

3. Отправьте письмо

Укажите возвращённый address как получателя в SMTP-клиенте вашего приложения.

SMTP host: smtp.tstly.ru
Port: 465 (SSL/TLS)
Alternative: 587 (STARTTLS)

Порт 25 остаётся доступен для SMTP relay, но для приложений и CI используйте 465 или 587: провайдеры часто блокируют исходящий 25.

Remote registration для E2E

Для CI выпустите в dashboard ключ с preset E2E_INBOX. Его минимальные scopes: inboxes:read, inboxes:write, messages:read и messages:delete. Ключ храните только в secret TSTLY_API_KEY.

Полный lifecycle ниже регистрирует адрес, извлекает его поля, ожидает письмо и обязательно удаляет inbox. Нужны curl, jq и uuidgen.

export TSTLY_API_KEY='…'
export IDEMPOTENCY_KEY="$(uuidgen)"

INBOX="$(curl --fail-with-body -sS -X POST https://api.tstly.ru/v1/inboxes \
  -H "Authorization: Bearer $TSTLY_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"localPart":"e2e-signup","retentionSeconds":3600}')"
INBOX_ID="$(printf '%s' "$INBOX" | jq -r .id)"
INBOX_ADDRESS="$(printf '%s' "$INBOX" | jq -r .address)"

# Trigger the consumer flow so it sends mail to $INBOX_ADDRESS, then:
curl --fail-with-body -sS   "https://api.tstly.ru/v1/inboxes/$INBOX_ID/messages/wait?subject=Confirm%20account&timeoutSeconds=30" \
  -H "Authorization: Bearer $TSTLY_API_KEY"

curl --fail-with-body -sS -X DELETE   "https://api.tstly.ru/v1/inboxes/$INBOX_ID" \
  -H "Authorization: Bearer $TSTLY_API_KEY"
Идемпотентный retry

Один registration intent использует один Idempotency-Key в течение всех повторов. Тот же key с другим body возвращает 409 IDEMPOTENCY_KEY_REUSED, а повтор после удаления inbox — 409 IDEMPOTENCY_RESOURCE_DELETED. Запись хранится 24 часа.

Для параллельных workers используйте читаемый prefix и случайный suffix в localPart, например pw-signup-2-0-a1b2c3d4. SMTP: smtp.tstly.ru:465 с implicit TLS либо smtp.tstly.ru:587 с STARTTLS.

Аутентификация

Передавайте API key как Bearer token. Ключ привязан к tenant и не даёт доступ к чужим inbox.

Authorization: Bearer $TSTLY_API_KEY
Не помещайте key в URL.

Храните его как secret CI/CD и отзывайте в dashboard, если он мог быть раскрыт.

Получение писем

Сначала получите текущий список, затем используйте latestCursor как afterCursor в long polling. Без cursor запрос ждёт первое доступное сообщение.

curl "https://api.tstly.ru/v1/inboxes/$INBOX_ID/messages/wait?timeoutSeconds=30" \
  -H "Authorization: Bearer $TSTLY_API_KEY"

Ответ 204 No Content означает, что за выбранные 30 секунд нового письма не появилось. Повторите запрос; это нормальный результат, не ошибка.

Коды и ссылки из письма

Получите код подтверждения или ссылку из первого подходящего письма. Укажите длину кода либо разрешённый origin и префикс пути ссылки. Примеры ниже используют вымышленные значения.

const code = await tstly.messages.waitForCode(inbox.id, {
  subject: "Confirm account", length: 6, timeoutSeconds: 30,
}); // строка: ведущие нули сохраняются

const link = await tstly.messages.waitForLink(inbox.id, {
  subject: "Confirm account",
  origin: "https://example.test", pathPrefix: "/confirm",
  timeoutSeconds: 30,
});

Один deadline 1–30 секунд охватывает ожидание и извлечение. Передайте signal вторым объектом options после input, чтобы отменить операцию. Несколько разных значений дают ошибку неоднозначности: SDK не выбирает первое наугад.

Из уже полученного письма

POST /v1/inboxes/{inboxId}/messages/{messageId}/extract
Authorization: Bearer $TSTLY_API_KEY
Content-Type: application/json

{"type":"code","length":6}

// 200: пример ответа
{"status":"success","candidates":[{"value":"001234","locations":[{"source":"plain","offset":6}]}]}

Нужен scope messages:read. Статусы результата: success, not_found, ambiguous и limit_exceeded. При превышении лимита значения не возвращаются. Вложения не анализируются, ссылки не открываются.

CLI и адаптеры

tstly message wait-code "$INBOX_ID" --length 6 --subject "Confirm account" --timeout 30
# По умолчанию выводятся только status и candidateCount.
# --reveal явно разрешает вывести код или ссылку.
tstly message wait-link "$INBOX_ID" --origin https://example.test --path-prefix /confirm --reveal

Playwright предоставляет tstly.waitForCode(input) и tstly.waitForLink(input). Cypress — cy.tstlyWaitForCode(inboxId, input) и cy.tstlyWaitForLink(inboxId, input): запрос выполняется в Node, запись команды в журнал отключена. API key храните только в Node/CI.

Коды и ссылки — секретные данные

Не записывайте их в логи, отчёты, screenshots и traces. По умолчанию Playwright прикладывает только безопасные метаданные; снимки экрана вашего приложения могут раскрыть введённый код.

Код: 4–12 ASCII-цифр отдельным токеном. Ссылка: HTTP(S), один разрешённый origin с учётом порта и граница сегмента пути. Относительные href требуют явного baseUrl того же origin. До 1 Mi UTF-16 символов plain + HTML, 20 разных значений, 8 мест на значение. Заняты все четыре слота API — 429 EXTRACTION_BUSY с Retry-After: 1.

Создание тестового письма · Пока выключено

API-приём реализован, но пока закрыт в production: сервер отвечает503 CAPTURE_DISABLED. Для действующего подключения используйте SMTP. После включения письмо будет сохраняться только в вашем ящике tstly; получателям из заголовка To ничего не доставляется.

Кабинет работает через сессию входа и не требует вставлять API-ключ. Для приложения или CI нужен отдельный ключ с правом messages:write; существующие ключи не получают это право автоматически. Чтение требуетmessages:read.

# После включения API-приёма. Сохраните ключ для явного повтора того же тела.
cat > synthetic.json <<'JSON'
{"from":"app@example.test","to":["user@example.test"],"subject":"Sandbox check","text":"Synthetic content"}
JSON
export CAPTURE_KEY="$(uuidgen)"
curl --fail-with-body -X POST "https://api.tstly.ru/v1/inboxes/$INBOX_ID/messages" \
  -H "Authorization: Bearer $TSTLY_API_KEY" \
  -H "Idempotency-Key: $CAPTURE_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @synthetic.json

JSON поддерживает from, to, необязательную тему, text и/или html, вложения в base64. Для готового MIME используйтеContent-Type: message/rfc822 и --data-binary @synthetic.eml. Размер запроса — до 10 МиБ; до 20 вложений общим размером до 5 МиБ.

Автоматических повторов нет. При неизвестном результате повторите запрос явно с тем же ключом и неизменённым телом в течение 24 часов. Отмена ожидания не отменяет сохранение письма. Если письмо уже удалено или истёк срок хранения, повтор возвращает 410 без нового приёма.

# SDK/CLI 0.1.4, после включения серверной возможности
tstly message capture "$INBOX_ID" --json synthetic.json --idempotency-key "$CAPTURE_KEY"
export RAW_CAPTURE_KEY="$(uuidgen)"
tstly message capture "$INBOX_ID" --raw synthetic.eml --idempotency-key "$RAW_CAPTURE_KEY"

TypeScript SDK и CLI

Официальный ESM-пакет @tstly/sdk работает в Node.js 22.12+. Клиент типизирует inbox, сообщения, вложения, usage и webhooks, поддерживает AbortSignal, timeout и безопасный retry чтения с учётом Retry-After.

Beta artifacts

SDK 0.1.7 и адаптеры 0.1.1 доступны как проверяемые tarball ниже. Публикация scope @tstly в npm registry ожидает внешней авторизации; обычная команда из registry пока не сработает.

curl -fLO https://tstly.ru/downloads/tstly-sdk-0.1.7.tgz
npm install ./tstly-sdk-0.1.7.tgz

import { Tstly } from "@tstly/sdk";

const tstly = new Tstly({ apiKey: process.env.TSTLY_API_KEY! });
const inbox = await tstly.inboxes.create({ retentionSeconds: 3600 });
const message = await tstly.messages.wait(inbox.id, {
  subject: "Confirm account",
  timeoutSeconds: 30,
});

CLI для CI

Передавайте ключ только через TSTLY_API_KEY. JSON-данные пишутся в stdout, диагностика — в stderr. Timeout ожидания завершает команду с кодом 3.

export TSTLY_API_KEY='…'
tstly inbox create --local-part ci --retention 3600
tstly message wait "$INBOX_ID" --subject "Confirm account" --timeout 30

Playwright и Cypress

Готовые адаптеры создают отдельный inbox на каждую попытку теста, изолируют параллельные workers и гарантируют cleanup. API key остаётся в Node-процессе и не передаётся странице или browser context.

Playwright fixture

curl -fLO https://tstly.ru/downloads/tstly-sdk-0.1.7.tgz
curl -fLO https://tstly.ru/downloads/tstly-playwright-0.1.1.tgz
npm install -D ./tstly-sdk-0.1.7.tgz ./tstly-playwright-0.1.1.tgz @playwright/test

import { test, expect } from "@tstly/playwright";
test("signup", async ({ page, tstly }) => {
  await page.getByLabel("Email").fill(tstly.inbox.address);
  expect(await tstly.wait({ subject: "Confirm account" })).not.toBeNull();
});

Cypress tasks

// cypress.config.ts — Node process
import { setupTstly } from "@tstly/cypress/node";
// support/e2e.ts — browser commands, no API key
import { installTstlyCommands } from "@tstly/cypress/commands";

При падении Playwright по умолчанию прикладывает только metadata. Режим artifactMode: "full" добавляет raw MIME и вложения — включайте его только для защищённых CI artifacts с ограниченным retention.

Webhooks

Подписка получает событие message.received как at-least-once доставку. Secret возвращается один раз. Проверяйте HMAC в X-Tstly-Signature, дедуплицируйте поX-Tstly-Event-Id и отвечайте 2xx.

curl -X POST https://api.tstly.ru/v1/webhooks \
  -H "Authorization: Bearer $TSTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.test/tstly","description":"CI events"}'

История delivery, ротация secret и ручной redelivery доступны в API и dashboard. Redirect и private-network targets отклоняются.

Справочник API

Открыть интерактивную документацию Scalar

Скачать OpenAPI JSON

Ошибки

Ошибка возвращается в едином JSON-формате с кодом, сообщением, request ID и ошибками полей.

{
  "code": "VALIDATION_ERROR",
  "message": "Request validation failed",
  "requestId": "…",
  "fieldErrors": []
}
400Некорректные параметры или JSON
401Key отсутствует, неверен или отозван
404Ресурс не найден в текущем tenant
409Конфликт состояния или лимит активных inbox
429Превышен rate/concurrency limit

Лимиты beta

Размер MIME10 MiB
Retention1 час — 7 дней
Активные inbox100 на tenant
Запросы API120/мин на key
Ожидания10 на tenant, до 30 сек.

Матрица совместимости

REST APIv1 beta · HTTPS/JSON
@tstly/sdk0.1.x · Node.js ≥22.12 · ESM
@tstly/playwright0.1.x · Playwright 1.50–1.x
@tstly/cypress0.1.x · Cypress 13–15
Beta artifactsSDK · Playwright · Cypress

Миграция alpha → beta

Смена base URL, API key и inbox address не требуется. Зафиксируйте SDK на ^0.1.7 и адаптеры на ^0.1.1, храните key только в Node/CI secret, обрабатывайте 204 long-poll timeout и 429с Retry-After. Для webhooks добавьте HMAC-проверку и идемпотентность event ID. HTML писем по-прежнему считается недоверенным содержимым.

HTTP-автотесты · В разработке

В кабинете откройте «Автотесты» → «Сценарии» → «Новый HTTP-сценарий». Редактор содержит вкладки «Запрос», «Проверки» и «Переменные», поддерживает светлую, тёмную и системную тему. Несохранённые черновики остаются только в памяти вкладки.

Сохранение и выполнение HTTP в production пока выключены. После отдельного включения запуск потребует сохранённую версию сценария и текущую ревизию окружения. До 20 последовательных шагов, общий таймаут до 300 секунд; только публичные HTTP(S) адреса на портах 80/443, без redirect и автоматического повтора запроса.

Доступны проверки статуса, заголовка, JSON по JSON Pointer и вхождения текста. Секреты привязываются по идентификатору и версии. Результат по умолчанию показывает статусы, длительность и размер ответа. Чувствительная диагностика требует явного opt-in и отдельного разрешения, ограничена TTL. Отмена не откатывает внешнее действие.

SDK 0.1.3 сохраняет v1 и добавляет явный namespace client.v2; CLI — tstly v2. Его наличие не означает включение HTTP на сервере.

Beta release notes

В beta входят quota/usage, cursor filters и long polling, подписанные webhooks, TypeScript SDK/CLI, Playwright и Cypress adapters, workspace roles/audit, operator abuse controls и external SMTP reliability monitoring.

Известные ограничения

Вход только через Яндекс; custom domains и IMAP отсутствуют; framework packages рассчитаны на Node.js ESM; webhook delivery at-least-once; npm scope ещё ожидает публикации, поэтому beta packages устанавливаются из versioned tarballs; сервис предназначен только для тестовых данных.

Безопасность, support и feedback

Не отправляйте реальные персональные или production-данные. Удаляйте inbox после теста и выбирайте минимальный retention. Уязвимости сообщайте приватно на security@tstly.ru, вопросы и feedback — на support@tstly.ru. Не прикладывайте API keys, raw MIME или содержимое писем.