Уведомления о заявках с формы
Контактная форма или форма заявки на лендинге — часто первый живой контакт с клиентом. Отвечать хочется быстро: свежий лид, ответивший через 5 минут, — это совсем другой конверт, чем «перезвоним завтра». Serverside-обработчик формы шлёт мгновенный пуш с коротким превью и ссылкой в админку. Плюс защита от ботов, чтобы телефон не звенел от спама.
Общие правила
Заголовок раздела «Общие правила»- Только на сервере. Форма постится на ваш бэкенд,
notify()вызываем оттуда — токен на клиенте держать нельзя. - Анти-спам обязателен. Публичная форма — магнит для ботов. Минимум: honeypot (скрытое поле) и rate-limit по IP.
- Короткое превью + ссылка. В пуш — имя, обрезанное сообщение и прямую ссылку в админку; всё остальное смотрим по клику.
Базовая обёртка
Заголовок раздела «Базовая обёртка»Берём notify() из обзорной страницы:
import os, requests
def notify(title, message, priority=5): requests.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, )Node.js (Express)
Заголовок раздела «Node.js (Express)»Honeypot-поле + rate-limit по IP, затем пуш с превью и ссылкой:
const {notify} = require('./notifly');
const lastByIp = new Map(); // ip -> ts, простой rate-limitconst MIN_INTERVAL = 30_000; // не чаще 1 заявки в 30с с одного IP
app.post('/contact', express.json(), async (req, res) => { const {name = '', email = '', message = '', website = ''} = req.body;
// 1) honeypot: поле website скрыто в CSS; заполнено — это бот if (website) return res.json({ok: true}); // делаем вид, что приняли
// 2) rate-limit по IP const now = Date.now(); if (now - (lastByIp.get(req.ip) || 0) < MIN_INTERVAL) { return res.status(429).json({error: 'too fast'}); } lastByIp.set(req.ip, now);
const lead = await saveLead({name, email, message});
notify( `📨 Заявка от ${name || email || 'аноним'}`, `${message.slice(0, 200)}\n\nОткрыть: https://admin.example.com/leads/${lead.id}`, 6 ); res.json({ok: true});});В разметке формы — скрытый honeypot, который люди не видят, а боты заполняют:
<form method="post" action="/contact"> <input name="name" placeholder="Имя"> <input name="email" placeholder="Email"> <textarea name="message"></textarea> <!-- honeypot: скрыто через CSS, реальный человек не заполнит --> <input name="website" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px"> <button>Отправить</button></form>Python (FastAPI)
Заголовок раздела «Python (FastAPI)»Тот же приём на FastAPI:
import timefrom collections import defaultdictfrom fastapi import FastAPI, Request, Formfrom notifly import notify
app = FastAPI()
last_by_ip: dict[str, float] = defaultdict(float)MIN_INTERVAL = 30 # не чаще 1 заявки в 30с с одного IP
@app.post("/contact")async def contact( request: Request, name: str = Form(""), email: str = Form(""), message: str = Form(""), website: str = Form(""), # honeypot): # 1) honeypot заполнен — это бот if website: return {"ok": True}
# 2) rate-limit по IP ip = request.client.host now = time.time() if now - last_by_ip[ip] < MIN_INTERVAL: return {"ok": True} # тихо игнорируем флуд last_by_ip[ip] = now
lead = await save_lead(name, email, message)
notify( f"📨 Заявка от {name or email or 'аноним'}", f"{message[:200]}\n\nОткрыть: https://admin.example.com/leads/{lead['id']}", 6, ) return {"ok": True}Что ещё присылать
Заголовок раздела «Что ещё присылать»- Заявки с горячих страниц (страница тарифов, «купить») — приоритет повыше: это ближе к продаже, чем общий контакт.
- Ежедневный дайджест «N заявок за сутки» — тихо (приоритет 3), чтобы видеть поток и не пропустить провал формы.
- Форма молчит. Если за N часов ни одной заявки, хотя обычно они есть, — возможно, форма сломана; сюда хорошо ложится heartbeat.
- Быстрый ответ = выше конверсия. Свежий лид в кармане через секунды после отправки.
- Спам не долетает. Honeypot и rate-limit отсеивают ботов до пуша.
- Всё под рукой. Превью в уведомлении, полная заявка — по ссылке в админку.