FormData собирает именованные поля формы. Для multipart не задавайте Content-Type вручную: браузеру нужно добавить boundary. Скрытое поле access_key входит в FormData вместе с email и сообщением.
Явный контракт ответа
Форма использует общий action Formhunt и скрытое поле access_key. Успех JSON подтверждают success: true и status: “accepted”; код ниже проверяет их вместе с HTTP статусом.
export async function sendForm(form) {
const response = await fetch(form.action, {
method: 'POST',
headers: { Accept: 'application/json' },
body: new FormData(form),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
if (result.success !== true || result.status !== 'accepted') throw new Error('Приём не подтверждён');
return result;
}
Вызов из интерфейса
Сначала проверьте форму, затем заблокируйте повторное нажатие и вызовите функцию из try/catch. Очищайте поля только после подтверждённого приёма. В finally верните кнопку в доступное состояние. В form сохраните общий action и подставьте свой access_key.
Ошибки, которые нужно различать
HTTP 400/422 обычно требует проверки данных по контракту сервера. HTTP 429 требует понятной паузы. Ошибка сети и неверный JSON не являются успехом. Ответ 200 с HTML вместо JSON тоже нужно обработать.
Не используйте no-cors ради исчезновения ошибки: непрозрачный ответ не даёт подтвердить приём. Проверьте синтетические ответы 201/accepted, 400, 429, 500 и сломанный JSON перед реальной отправкой.
Источники и дата проверки
Редакционная сверка: 2026-10-27. Учитывайте версию инструментов в своём проекте.
Обновлено