Contact form and lead notifications
A contact or lead form on a landing page is often the first live touch with a customer. You want to respond fast: a fresh lead replied to within 5 minutes is a very different conversion than “we’ll call you back tomorrow”. A server-side form handler sends an instant push with a short preview and a link to the admin panel. Plus bot protection so the phone doesn’t ring from spam.
General rules
Section titled “General rules”- Server-side only. The form posts to your backend; call
notify()from there — you can’t keep the token on the client. - Anti-spam is mandatory. A public form is a magnet for bots. At minimum: a honeypot (hidden field) and a per-IP rate limit.
- Short preview + link. Put the name, a truncated message, and a direct link to the admin panel in the push; everything else we view on click.
Base wrapper
Section titled “Base wrapper”We take notify() from the overview page:
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)
Section titled “Node.js (Express)”A honeypot field + a per-IP rate limit, then a push with a preview and a link:
const {notify} = require('./notifly');
const lastByIp = new Map(); // ip -> ts, a simple rate limitconst MIN_INTERVAL = 30_000; // no more than 1 lead per 30s from one IP
app.post('/contact', express.json(), async (req, res) => { const {name = '', email = '', message = '', website = ''} = req.body;
// 1) honeypot: the website field is hidden via CSS; if filled, it's a bot if (website) return res.json({ok: true}); // pretend we accepted it
// 2) per-IP rate limit 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});});In the form markup, a hidden honeypot that humans don’t see but bots fill in:
<form method="post" action="/contact"> <input name="name" placeholder="Имя"> <input name="email" placeholder="Email"> <textarea name="message"></textarea> <!-- honeypot: hidden via CSS, a real person won't fill it --> <input name="website" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px"> <button>Отправить</button></form>Python (FastAPI)
Section titled “Python (FastAPI)”The same trick on 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 # no more than 1 lead per 30s from one IP
@app.post("/contact")async def contact( request: Request, name: str = Form(""), email: str = Form(""), message: str = Form(""), website: str = Form(""), # honeypot): # 1) honeypot filled — it's a bot if website: return {"ok": True}
# 2) per-IP rate limit ip = request.client.host now = time.time() if now - last_by_ip[ip] < MIN_INTERVAL: return {"ok": True} # silently ignore the flood 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}What else to send
Section titled “What else to send”- Leads from hot pages (pricing page, “buy”) — a higher priority: this is closer to a sale than a general contact.
- A daily digest “N leads per day” — quietly (priority 3), to see the flow and not miss a broken form.
- The form goes silent. If there’s been no lead for N hours when there usually are, the form may be broken; a heartbeat fits well here.
Benefits
Section titled “Benefits”- A fast reply = higher conversion. A fresh lead in your pocket seconds after submission.
- Spam doesn’t get through. The honeypot and rate limit filter out bots before the push.
- Everything at hand. A preview in the notification, the full lead via the admin link.