Form EZPM-1. Self-hosted rent collection Issued by Qureshi Inc. License AGPL-3.0 v2026.05 · Production-ready
PURPOSE Rent collection software · Stripe-native · Self-hosted

Rent collected on schedule. Without the SaaS markup.

Tenants pay rent online; Stripe Subscriptions charges the right amount on the right day, every month. You run the server. You own the data. EZPM adds zero per-tenant fees on top of Stripe’s standard rates.

ACH fee
0.8 % · cap $5
Auth
Zitadel OIDC
Deploy
docker compose
ACH cap
$5
any rent amount, capped
Bank settle
~4d
card auths instantly
SaaS fee
0
per tenant, per month, ever
License
AGPL
source open, fork freely
§ 01

Features

Working code for the whole rental lifecycle — payments, people, maintenance, documents, and the ops glue around them. Each article is something you would otherwise build, debug, and re-debug yourself.
Art. I

Stripe ACH & card processing

Bank account at 0.8 % capped at $5, card at 2.9 % + $0.30. Everything through Stripe. PCI scope: zero. Account numbers never touch your server.

Art. II

Auto-pay via Subscriptions

Stripe Subscriptions owns the schedule. Server can be offline at charge time and rent still bills. Webhooks reconcile on the way back up; a CLI catches anything missed during multi-day outages.

Art. III

Bank verification, both paths

Instant verification via Stripe Financial Connections. Microdeposit fallback for banks that need it. Either path lands the same verified PaymentMethod.

Art. IV

Zitadel OIDC auth

Email + password, passkeys, or federated identity. Auth.js v5 JWE cookies; federated logout kills both sessions. Admins can fire a Zitadel password-reset email from the tenant page.

Art. V

Maintenance requests with photos

Tenants report issues with photos from their phone; admins move status open → in progress → resolved. Files get UUID names, server-side type/size checks, and ownership-checked serving — no public URLs.

Art. VI

Two-way maintenance thread

A comment thread per request. Replies sync both directions with Mattermost; status shows as a single emoji reaction (🔨/✅/🚫). Reply from chat, it lands in the app and emails the tenant.

Art. VII

Documents

A per-tenant folder both sides upload to — lease, insurance, proof of income, notices. PDF, images, and office files up to 25 MB, served only through an ownership-checked route.

Art. VIII

Announcements

Post a notice every tenant sees on their dashboard, optionally emailed to all of them in one click. Water shut-offs, reminders, policy changes.

Art. IX

Branded email, opt-out per tenant

Receipts, maintenance status, and reply emails on one shared template, sent over any SMTP relay. Each tenant turns off the emails they don’t want in their settings.

Art. X

Mattermost ops integration

Signups, subscriptions, charges, and failures post to a channel. Each maintenance request is its own thread. An optional WebSocket bridge gives you two-way replies — works with a private channel.

Art. XI

Admin analytics dashboard

KPI cards plus charts for revenue, payments, tenant growth, and maintenance, over 1M / 3M / 6M / 1Y / 2Y / Max. Every card clicks through to the exact filtered list.

Art. XII

Prometheus & Grafana ready

A token-guarded /api/metrics endpoint exposes business gauges in Prometheus format — scrape it, graph it in Grafana today. Runtime metrics drop into the same endpoint later.

Art. XIII

Webhook idempotency

Every Stripe event INSERTs into a unique-id table with ON CONFLICT DO NOTHING. Retries become no-ops. A reconcile CLI replays anything Stripe couldn’t deliver during an outage.

Art. XIV

Self-hosted, Next.js 15

App Router, React 19, TypeScript. Docker compose for postgres + PostgREST + Caddy. Coolify-friendly, runs on a $5/mo VPS or a home server behind a Cloudflare tunnel.

Art. XV

AGPL-3.0 licensed

Source available. Modify, redeploy, fork. Derivative SaaS must release modifications. No license server, no callbacks, no telemetry.

§ 02

Procedure

Bring a domain, a Stripe account, and a Zitadel instance. Everything else is automated by the schema, the webhook handler, and the admin form.
  1. 01

    Deploy with docker compose

    Coolify, Render, a home server — anywhere docker runs. One compose file for the app, one for the database stack. ~10 minutes from clone to running.

  2. 02

    Connect Stripe and Zitadel

    Drop your Stripe keys and webhook secret into the env. Register the OIDC callback URL in Zitadel. The first user who logs in becomes admin, atomically.

  3. 03

    Pre-stage tenants by email

    Admin enters tenant email, property, and rent. Zitadel sends the invite. Tenant sets a password and lands on their dashboard automatically — no second login screen.

  4. 04

    Tenant adds bank or card

    Stripe PaymentElement handles the form. Bank picker for instant ACH verification; manual entry falls through to microdeposits. Subscription auto-creates on the first verified method.

  5. 05

    Stripe charges on the due day

    Every month, automatically. Webhooks update the payments mirror. Failed charges go to Stripe Smart Retries; bounced ACH and disputes fire normal events you can see in the admin.

§ 03

Schedule of fees

EZPM charges nothing. The only money that moves is what Stripe takes per transaction — identical to a direct Stripe integration.
Payment method Stripe rate $1,500 rent $2,500 rent $5,000 rent
Bank account (ACH) us_bank_account · Stripe Financial Connections 0.8 %, capped at $5.00 $ 5.00 $ 5.00 $ 5.00
Credit / debit card card · all major networks 2.9 % + $0.30 $ 43.80 $ 72.80 $ 145.30
EZPM software fee that's the whole pitch $ 0.00 $ 0.00 $ 0.00

Configuration determines who absorbs the fee. By default the tenant sees it as a line item on top of rent; flip a setting and the property owner absorbs it instead. Bank account is dramatically cheaper at any rent above ~$625, where the $5 ACH cap takes over.

§ 04

Inventory

Every component is something you could read, replace, or fork. Nothing proprietary. Nothing magical.
Framework
Next.js 15
App Router, React 19, server actions, edge middleware. TypeScript end-to-end.
Data
Postgres · PostgREST
Self-hosted database with an auto-generated REST API. Same shape as Supabase, runs on your hardware.
Payments
Stripe
Customers, Subscriptions, Invoices, Financial Connections. API version pinned, dahlia-compatible.
Identity
Zitadel
Self-hosted OIDC provider. Auth.js v5 client. Federated identity (Google, Authentik) optional.
UI kit
Tailwind · shadcn/ui
Familiar primitives. Copy any component into your fork and modify directly — no opaque package boundary.
Edge
Caddy
Reverse proxy with automatic TLS. The DB stack uses it to map Supabase-shaped URLs to plain PostgREST.
Email
SMTP · nodemailer
Branded receipts, status, and reply emails over any SMTP relay (Brevo, SES, Postfix). Unset the keys and email turns off.
Notifications
Mattermost
Bot API for per-request threads + emoji status. Optional WebSocket bridge ships in-repo for two-way replies.
Charts
Recharts
Dashboard graphs for revenue, payments, tenants, and maintenance, code-split into the admin route only.
Observability
Prometheus
Token-guarded /api/metrics in Prometheus text format. Point Grafana at it; add runtime metrics to the same endpoint.
Runtime
Docker compose
App + DB stack, plus optional Zitadel and the Mattermost bridge. Coolify-friendly. Works on any docker host.
License
AGPL-3.0
Source available. Modify, redeploy, fork. SaaS forks must release modifications.
Production · 3 properties · 0 missed months

Stop chasing checks. Start collecting rent.

A weekend to deploy. A few minutes to onboard each tenant. Stripe takes over from there. Your weekends go back to running properties, not chasing payments.