# MidnightService Mailer API — руководство по интеграции Документ для разработчиков, подключающих внешние приложения, скрипты и CRM к API отправки писем Wallapop. **Версия API:** 1.1.0 **Формат:** REST, JSON **Кодировка:** UTF-8 --- ## 1. Базовый URL Замените на адрес вашего развёрнутого сервиса: ``` https://ВАШ_ДОМЕН/api/v1/mailer ``` Пример (публичный инстанс): `https://api.midnightchamberx.com/api/v1/mailer` Полные пути эндпоинтов: | Метод | Путь | |-------|------| | POST | `{BASE}/send` | | POST | `{BASE}/send-manual` | | POST | `{BASE}/shorten` | | GET | `{BASE}/templates` | | GET | `{BASE}/languages` | Дополнительно (корень приложения, без префикса mailer): | Метод | Путь | Описание | |-------|------|----------| | GET | `https://ВАШ_ДОМЕН/health` | Проверка живости (токен **не** нужен) | | GET | `https://ВАШ_ДОМЕН/openapi.json` | Спецификация OpenAPI 3 (токен **не** нужен) | --- ## 2. Аутентификация ### Заголовок Каждый запрос к `/api/v1/mailer/*` должен содержать: ```http X-MIDNIGHT-TOKEN: <ваш_ключ> ``` Также требуется заголовок для JSON-тела: ```http Content-Type: application/json ``` Глобальный HTTP-middleware отклоняет запросы **без** заголовка `X-MIDNIGHT-TOKEN` с кодом **401** и телом: ```json {"detail": "Api token is required"} ``` Исключения (токен не проверяется middleware на уровне пути): `GET /`, `GET /health`, `GET /openapi.json`, `OPTIONS`, пути с префиксом `/admin/`, статика `/docs/`. ### Типы ключей Поддерживаются: 1. **API-ключ** — выдаётся после одобрения заявки в Telegram-боте (формат вроде `midnight_...`). Хранится в БД API. 2. **Ключ подписки (subscription key)** — активированный ключ с неистёкшим сроком; должен быть в состоянии «использован» (`key_active == false` в логике проверки — неактивированный ключ для активации не подходит). При неверном или просроченном ключе эндпоинты мэйлера отвечают **401** с телом: ```json {"detail": "Authorization needed"} ``` --- ## 3. Общий формат ответа отправки Эндпоинты `POST /send` и `POST /send-manual` возвращают объект **MailerResponse**: ```json { "success": true, "message": "OK", "details": { } } ``` - `success` — факт успешной отправки SMTP. - `message` — текст результата или описание ошибки (например, текст исключения SMTP, ошибка парсинга). - `details` — словарь с метаданными (может быть пустым при ошибке до отправки). ### Поля `details` при успехе **После `POST /send`:** | Поле | Тип | Описание | |------|-----|----------| | `item_name` | string | Название товара с Wallapop | | `price` | string | Цена | | `currency` | string | Валюта (например EUR) | | `order_id` | string | Сгенерированный номер заказа в письме | | `lang` | string | `es` или `it` | | `template` | string | Имя файла шаблона | | `from` | string | SMTP-адрес отправителя | **После `POST /send-manual`:** | Поле | Тип | Описание | |------|-----|----------| | `item_name` | string | Переданное название | | `price` | string | Переданная сумма/цена | | `order_id` | string | Номер заказа | | `template` | string | Имя шаблона | | `from` | string | SMTP-адрес отправителя | При ошибке валидации или логики сервер может вернуть **400** / **403** с полем `detail` или `details` (см. раздел 7). --- ## 4. Эндпоинты ### 4.1. `POST /send` — автоотправка по ссылке Wallapop Сервер сам запрашивает страницу объявления, подставляет название и цену в шаблон. **Тело запроса:** ```json { "listing_url": "https://es.wallapop.com/item/...", "to_email": "buyer@example.com", "link": "https://example.com/your-landing-page", "lang": null, "shorten_link": false } ``` | Поле | Обязательное | Описание | |------|--------------|----------| | `listing_url` | да | URL вида `https://<поддомен>.wallapop.com/...` | | `to_email` | да | Email получателя | | `link` | да | URL кнопки в письме | | `lang` | нет | `es` или `it`; если не указан — определяется по поддомену в URL | | `shorten_link` | нет | `true` — сократить `link` через Short.io перед вставкой в письмо | ### 4.2. `POST /send-manual` — ручные данные **Тело запроса:** ```json { "to_email": "buyer@example.com", "template": "wallapop_it.html", "link": "https://example.com/pay", "item_name": "Scarpe Nike", "amount": "45", "from_name": "Wallapop", "shorten_link": false } ``` | Поле | Обязательное | Описание | |------|--------------|----------| | `to_email` | да | Email получателя | | `template` | да | Имя файла шаблона (см. `GET /templates`) | | `link` | да | URL кнопки | | `item_name` | да | Название товара | | `amount` | да | Цена/сумма (строка) | | `from_name` | нет | Имя отправителя в письме, по умолчанию `Wallapop` | | `shorten_link` | нет | Сокращение `link` через Short.io | ### 4.3. `POST /shorten` **Тело:** ```json { "url": "https://example.com/long-path" } ``` **Ответ:** ```json { "success": true, "short_url": "https://your-short-domain/abc123", "original_url": "https://example.com/long-path" } ``` ### 4.4. `GET /templates` Ответ: ```json { "templates": ["wallapop_es.html", "wallapop_it.html"] } ``` ### 4.5. `GET /languages` Ответ — объект, ключи `es`, `it`, значения: `template`, `name`. --- ## 5. Примеры кода ### cURL — отправка по Wallapop ```bash curl -X POST "https://api.example.com/api/v1/mailer/send" \ -H "Content-Type: application/json" \ -H "X-MIDNIGHT-TOKEN: YOUR_TOKEN_HERE" \ -d '{ "listing_url": "https://it.wallapop.com/item/...", "to_email": "user@gmail.com", "link": "https://example.com/checkout", "shorten_link": true }' ``` ### Python (requests) ```python import requests BASE = "https://api.example.com/api/v1/mailer" TOKEN = "YOUR_TOKEN_HERE" headers = { "X-MIDNIGHT-TOKEN": TOKEN, "Content-Type": "application/json", } r = requests.post( f"{BASE}/send", headers=headers, json={ "listing_url": "https://es.wallapop.com/item/xxx", "to_email": "buyer@example.com", "link": "https://example.com/pay", "shorten_link": True, }, timeout=60, ) data = r.json() print(data["success"], data.get("message"), data.get("details")) ``` ### JavaScript (fetch) ```javascript const BASE = "https://api.example.com/api/v1/mailer"; const TOKEN = "YOUR_TOKEN_HERE"; const res = await fetch(`${BASE}/send`, { method: "POST", headers: { "Content-Type": "application/json", "X-MIDNIGHT-TOKEN": TOKEN, }, body: JSON.stringify({ listing_url: "https://it.wallapop.com/item/...", to_email: "buyer@example.com", link: "https://example.com/pay", shorten_link: false, }), }); const data = await res.json(); if (!res.ok) throw new Error(JSON.stringify(data)); console.log(data.success, data.details); ``` ### C# (HttpClient) ```csharp using var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-MIDNIGHT-TOKEN", "YOUR_TOKEN_HERE"); var body = new { listing_url = "https://es.wallapop.com/item/...", to_email = "buyer@example.com", link = "https://example.com/pay", shorten_link = false }; var json = JsonSerializer.Serialize(body); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://api.example.com/api/v1/mailer/send", content); var text = await response.Content.ReadAsStringAsync(); ``` ### PHP ```php true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-MIDNIGHT-TOKEN: YOUR_TOKEN_HERE', ], CURLOPT_POSTFIELDS => json_encode([ 'listing_url' => 'https://it.wallapop.com/item/...', 'to_email' => 'buyer@example.com', 'link' => 'https://example.com/pay', 'shorten_link' => false, ]), CURLOPT_RETURNTRANSFER => true, ]); $out = curl_exec($ch); curl_close($ch); $data = json_decode($out, true); ``` --- ## 6. Рекомендации по интеграции 1. **Таймауты** — парсинг Wallapop и SMTP могут занимать десятки секунд; ставьте HTTP-таймаут не меньше **60 с**. 2. **Повторные запросы** — при сетевых сбоях используйте идемпотентность на своей стороне (один и тот же вызов может повторно отправить письмо). 3. **CORS** — на сервере разрешены широкие CORS-настройки; для вызова из браузера всё равно учитывайте политику ключей (ключ в фронтенде раскрывается пользователю — **не рекомендуется**). 4. **Хранение ключа** — держите `X-MIDNIGHT-TOKEN` в переменных окружения или секрет-хранилище, не коммитьте в репозиторий. 5. **OpenAPI** — импортируйте `GET /openapi.json` в Postman, Insomnia, Swagger UI для автогенерации клиентов. 6. **Прокси и парсинг** — успешность `POST /send` зависит от доступности Wallapop с сервера (часто нужен HTTP-прокси на стороне инстанса). --- ## 7. Коды ошибок (кратко) | HTTP | Когда | |------|--------| | 400 | Невалидные поля (URL, email и т.д.), часто `{"detail": "..."}` | | 401 | Нет заголовка токена / неверный или неподходящий ключ | | 403 | Особые случаи валидации FastAPI (несколько отсутствующих полей) | | 200 | Тело `MailerResponse`; при неуспехе отправки может быть `success: false` и текст в `message` | Успешный HTTP **200** не гарантирует `success: true` — всегда проверяйте поле `success`. --- ## 8. Связанные файлы в репозитории - Роуты: `MailerRouters.py` - Модели запросов/ответов: `Config.py` - Проверка токена: `auth.py` - Точка входа FastAPI: `main.py` --- *Документ можно распространять вместе с бинарной сборкой или ссылкой на развёрнутый `/docs`.*