На aaPanel, Beget и похожих панелях проще всего положить contact.php рядом со статикой. Vite копирует всё из public/ в dist/, поэтому файл оказывается в dist/api/contact.php после сборки.
Минимальный контракт
- POST + Accept: application/json
- Поля name, email, message
- Скрытый honeypot — боты его заполняют, люди нет
- Ответ { ok: true } или { ok: false, error: "..." }
Почта только на сервере
Адрес получателя живёт в PHP (или в .env вне веб-корня). В HTML и JS его быть не должно — иначе его сразу заберут сканеры.
Деплой
rsync dist/ на веб-корень с исключениями для .user.ini, .htaccess, .env. Если на сервере правите локальный конфиг рядом с api/ — исключите его из --delete или держите секреты вне dist.