Форма обратной связи проходит как минимум три границы: ввод в браузере, приём backend и запись заявки или запрос к почтовому провайдеру. Надпись «Отправлено» должна соответствовать тому этапу, который система действительно подтвердила.
Проверь контракт запроса
Пусть форма отправляет имя, адрес для ответа и текст на собственный endpoint. Браузерная проверка длины и формата помогает исправить ошибку до отправки, но backend обязан проверить те же поля повторно: endpoint можно вызвать без страницы. Ограничь размер запроса, валидируй тип содержимого и добавь ограничение частоты. Сервер возвращает безопасный request ID и статус без внутренней ошибки SMTP, токена или stack trace.
HTTP 202 означает, что конкретный сервер принял запрос к дальнейшей обработке; сам статус не подтверждает доставку письма. Справочник MDN по HTTP status codes описывает результат HTTP-запроса, а не состояние почтового ящика.
Секрет зависит от сборщика
Ключ почтового API должен оставаться в backend. В Vite переменные с префиксом VITE_ включаются в клиентский код при сборке. В Next.js аналогичную публичную роль выполняет NEXT_PUBLIC_. Не помещай секрет под эти префиксы: он станет доступен браузеру. Документация Vite по Env Variables and Modes и Next.js по environment variables описывают эти конкретные правила.
Для backend-переменных используй секретное хранилище среды выполнения и различные тестовые/production credentials. Проверь результат production-сборки и browser DevTools: секрет не должен присутствовать в JS, HTML, запросе браузера или публичном логе.
Отличай принятие от доставки
Определи статус каждой ступени: заявка принята приложением; сообщение поставлено провайдеру; провайдер сообщил результат; сообщение появилось в контролируемом целевом ящике. Если endpoint только записал обращение, пиши «Заявка принята». Не обещай «письмо доставлено» по одному ответу HTTP.
Для диагностики достаточно request ID, времени, этапа и кода ответа; не логируй полный текст формы, если он не нужен. OWASP Logging Cheat Sheet отдельно описывает данные, которые следует исключать из логов. Повторная отправка после таймаута может создать дубль: выбери idempotency key или собственную дедупликацию по request ID, сохраняя введённый текст, если запрос не прошёл.
Проверяй один успешный и два отказных сценария на тестовом адресе или в sandbox провайдера: backend недоступен и провайдер отказал. Запись в базе и приём письма — разные подтверждения. Эта схема учебная; интеграция здесь не запускалась.
