Deployment on Yandex Cloud
The cloud version of Notifly runs entirely on Yandex Cloud serverless services: there are no always-on VMs, billing is only for actual traffic and storage.
This document describes how the deployed stack is organized and how to reproduce it in your own folder.
Architecture
Section titled “Architecture”┌──────────────────────────────────────────────────────────────────────────┐│ Клиент ││ (браузер · Android · desktop · MCP-агент · cron · веб-сайт · SMTP) │└──────┬──────────────┬──────────────┬───────────────┬────────────────────┘ │ HTTPS REST │ WSS │ POST /script │ SMTP ▼ ▼ ▼ ▼┌──────────────────────────────────────────────────────────────────────────┐│ Yandex Cloud API Gateway (notifly-ws-gateway) ││ ─ /api/* → notifly-api (REST) ││ ─ /ws → notifly-ws-handler (WebSocket) ││ ─ /script/* → notifly-api (публичные web-script триггеры) │└──────┬─────────────────────┬──────────────────────────┬─────────────────┘ │ │ │ ▼ ▼ ▼┌──────────────┐ ┌──────────────────┐ ┌────────────────────────────┐│ notifly-api │ │ notifly-ws- │ │ notifly-email ││ (golang121) │ │ handler │ │ (Mail Trigger → Email Inbox)│└─────┬────────┘ │ (golang123) │ └──────────┬─────────────────┘ │ └──────┬───────────┘ │ │ │ │ ▼ ▼ ▼┌──────────────────────────────────────────────────────────────────────────┐│ YDB Serverless (notifly-db) ││ users · applications · clients · messages · webhooks · email_inboxes ││ web_scripts · heartbeats · monitors · mcp_tokens · channel_shares │└──────────────────────────────────────────────────────────────────────────┘ ▲ ┌──────────────────────────┴──────────────────────────┐ │ Object Storage notifly-ws-connections │ │ connections/<id>.json — состояние WebSocket-сессий │ └──────────────────────────────────────────────────────┘
Timer triggers (cron `* * * * ? *`): • notifly-heartbeat — проверяет просроченные heartbeat-ы • notifly-monitor — выполняет активные проверки HTTP/TCP/TLS/...| Component | Resource | Type |
|---|---|---|
notifly-api | Cloud Function | REST + Web Script triggers |
notifly-ws-handler | Cloud Function | WebSocket handler |
notifly-email | Cloud Function | receiving emails via Mail Trigger |
notifly-heartbeat | Cloud Function | timer check of heartbeats |
notifly-monitor | Cloud Function | timer check of active monitors |
notifly-ws-gateway | API Gateway | single entry point |
notifly-db | YDB Serverless | all business logic |
notifly-ws-connections | Object Storage | state of WebSocket connections |
What’s missing in the cloud
Section titled “What’s missing in the cloud”When migrating from the self-hosted version the following were deliberately excluded:
- Go plugins (
.so) — the YC Functions runtime does not support them. See Introduction to plugins for alternatives. - OIDC — authorization is only local (BasicAuth + Bearer tokens).
- Embed UI — the frontend is deployed separately to the
app.notifly.rubucket. - Image upload — a stub (uploading images to Object Storage — on the roadmap).
- Fractional sort keys for messages — simplified to a string field.
Preparation
Section titled “Preparation”You will need:
- a Yandex Cloud account with a folder;
- the
ycCLI (installation); - a service account with roles
serverless.functions.admin,ydb.admin,storage.editor,api-gateway.admin; - static access keys for the service account (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) — for S3 from Cloud Function; - an IAM token (short-lived, 12 hours) — for the deployment itself.
All secrets are kept in .env at the repository root and are loaded via
set -a; source .env; set +a before running the deploy scripts.
Deployment steps
Section titled “Deployment steps”1. Create YDB and tables
Section titled “1. Create YDB and tables”yc ydb database create notifly-db --serverless# .env: YDB_DSN=grpcs://ydb.serverless.yandexcloud.net:2135/?database=...
cd server_yago run ./cmd/initdb # creates schema + admin/adminThe initdb script is idempotent: rerunning it won’t break existing
tables or recreate the admin.
2. Create S3-bucket for WebSocket
Section titled “2. Create S3-bucket for WebSocket”yc storage bucket create --name notifly-ws-connections --max-size 10737418243. Deploy REST + WebSocket + API Gateway
Section titled “3. Deploy REST + WebSocket + API Gateway”cd server_yabash deploy/deploy.shThe script:
- Builds a zip for
notifly-apiand uploads it as a Cloud Function; - Builds a zip for
notifly-ws-handlerand uploads it as a Cloud Function; - Applies a unified OpenAPI specification to the API Gateway
(REST
/{proxy+}+ WebSocket/ws).
If you redeploy
ws-handler/deploy.shseparately — it will overwrite the API Gateway with a WS-only spec; to restore REST, rerunserver_ya/deploy/deploy.sh.
4. Deploy timer functions (heartbeat, monitor)
Section titled “4. Deploy timer functions (heartbeat, monitor)”cd server_yabash deploy/deploy_heartbeat.shbash deploy/deploy_monitor.shEach script creates a Cloud Function and attaches a timer-trigger
with the schedule * * * * ? * (once per minute). Inside the function a
SELECT WHERE next_check_at <= now() is executed using the corresponding YDB index.
5. Deploy email function (optional)
Section titled “5. Deploy email function (optional)”cd server_yabash deploy/deploy_email.shAfter deployment, in the console bind the notifly-email function
to the desired mailbox (<local>+<alias>@<domain>). User mailboxes
are managed via Email Inbox — aliases are stored in YDB,
and Mail Trigger routes emails based on the subaddress part.
6. Deploy frontend
Section titled “6. Deploy frontend”cd admin_yanpm cinpm run buildbash deploy.sh # synchronizes dist/ to the app.notifly.ru bucketSee also admin deployment instructions — this step is largely automated.
Smoke test
Section titled “Smoke test”After deployment run:
bash server_ya/deploy/smoke_test.shpython3 server_ya/deploy/ws_test.pyCheck:
GET /health→{"health":"green","database":"green"}POST /auth/local/login(Basic admin/admin)POST /application+POST /message×NGET /message,GET /application/{id}/messagewss://.../ws?token=<C...>— handshake101,ping → pong,statuswithconnection_idandsource_ip.
Operations
Section titled “Operations”| Action | Where to check |
|---|---|
| Function logs | yc logging read --group-id <log-group> --format json |
| Function metrics | Yandex Cloud Console → Cloud Functions → metrics |
| YDB table state | YDB UI or yc ydb yql ... |
| Active WS connections | aws s3 ls s3://notifly-ws-connections/connections/ |
The stack is optimized for the FREE-tier:
- YDB Serverless — Request Units, no always-on storage; indexes on
next_check_atprovide O(1) checks in the heartbeat/monitor timers. - Cloud Functions — billed by millisec-execution. Functions wake up
only on activity (
POST /message, ping, cron). - Object Storage — a few bytes per WebSocket connection
(
connections/<id>.json~200 bytes). - API Gateway — billed per request.
With zero activity Notifly consumes nothing except the fee for reserved space in YDB (~10 ₽/month on the free-tier).