New signup and first-sale notifications
While the project is small, every new user — and especially the first payment — is an event. Opening a dashboard is a chore, but knowing “someone just signed up right now” is both pleasant and useful. Notifly turns this into a push: a daily digest “N new users” plus a separate loud signal about the very first sale.
General rules
Section titled “General rules”- Server-side only. Hook the signup handler and the payment webhook, not the client — otherwise the token leaks and anyone can push you.
- Digest vs. storm. If there are lots of signups, don’t push on each one — accumulate a counter and send it once a day. Keep the instant push for payments.
- The first sale is an event. Mark it separately and louder: it happens once.
Base wrapper
Section titled “Base wrapper”We take notify() from the overview page — in Python it’s two lines:
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)”The signup hook only bumps a counter, and a cron job sends the digest once a day:
const {notify} = require('./notifly');
let signupsToday = 0;
// in the signup handlerapp.post('/signup', async (req, res) => { const user = await createUser(req.body); signupsToday++; res.json({ok: true});});
// daily digest at 21:00 (node-cron)const cron = require('node-cron');cron.schedule('0 21 * * *', () => { if (signupsToday === 0) return; // quiet day — don't make noise notify('📈 Новые пользователи', `Сегодня зарегистрировалось: ${signupsToday}`, 4); signupsToday = 0;});An instant loud push — only on the very first payment:
const {notify} = require('./notifly');
// in the successful-payment handler (or in the payment provider's webhook)async function onPaymentSucceeded(payment) { const isFirstEver = await db.payments.count() === 1; // this is the project's first sale if (isFirstEver) { notify( '🎉 Первая продажа!', `${payment.amount} ${payment.currency} от ${payment.email}. Оно работает!`, 9 ); } else { notify('💰 Оплата', `${payment.amount} ${payment.currency} от ${payment.email}`, 5); }}Python (FastAPI)
Section titled “Python (FastAPI)”from fastapi import FastAPIfrom notifly import notify
app = FastAPI()signups_today = 0
@app.post("/signup")async def signup(payload: dict): global signups_today await create_user(payload) signups_today += 1 return {"ok": True}We hand out the digest on a schedule (APScheduler):
from apscheduler.schedulers.background import BackgroundScheduler
def daily_digest(): global signups_today if signups_today == 0: return # quiet day — stay silent notify("📈 Новые пользователи", f"Сегодня зарегистрировалось: {signups_today}", 4) signups_today = 0
sched = BackgroundScheduler()sched.add_job(daily_digest, "cron", hour=21) # every day at 21:00sched.start()An instant signal about the first sale:
async def on_payment_succeeded(payment): is_first_ever = await payments_count() == 1 # first sale of all time if is_first_ever: notify( "🎉 Первая продажа!", f"{payment['amount']} {payment['currency']} от {payment['email']}. Оно работает!", 9, ) else: notify("💰 Оплата", f"{payment['amount']} {payment['currency']} от {payment['email']}", 5)What else to send
Section titled “What else to send”- Activation, not just signup. A push when a new user reaches the key action (“created their first project”) is more honest than the signup itself.
- Plan upgrade. Moving from free to paid is a nice event, priority 5–6.
- Unsubscribe/refund. Also worth knowing, but quietly (priority 3), so you can ask “what went wrong” in time.
- Round numbers. The 100th, 1000th user — a separate loud push.
Benefits
Section titled “Benefits”- Dopamine for the indie developer. “Someone just signed up” and especially “first sale” is the best motivation to keep going.
- The project’s pulse in your pocket. A once-a-day digest shows the trend without opening a dashboard.
- No storm. Signups go by digest, payments go point-by-point; the phone doesn’t ring for no reason.