Muneem ERP
A complete ERP platform for one company — accounting, inventory, manufacturing, school, learning, CRM, HR and payments in a single branded workspace.
- Role
- Full-Stack Engineer & ERP Specialist
- Year
- 2026
- Category
- ERP / SaaS
- Platform
- Web App
- Stack
- FrappeERPNextPythonMariaDBRedisVue 3nginxAWSCloudflare

Why I built this
The company behind Muneem wanted one cohesive business stack — accounting, inventory and manufacturing, sales and procurement, HR and payroll, school fee management, online learning, and customer relationships — without juggling disconnected tools. Muneem delivers all of it on a single platform, and the roadmap points at an agentic ERP: a natural-language copilot that executes permission-checked operations over chat, with confirmation gates and a full audit trail. The flagship use case is a school principal asking 'which students are behind on fees?' and dispatching reminder emails in one conversation.
How it works
Finance core with a three-ledger model
GL Entry, Payment Ledger Entry and Advance Payment Ledger Entry keep accounting consistent through a full payment-request lifecycle (Draft → Initiated → Requested → Partially Paid → Payment Ordered → Paid → Cancelled), with bank reconciliation synced from Plaid.
Muneem School fee engine
Student admission, programs, assessments, attendance and grading — with an outstanding-amount tracking fee engine and its own Razorpay billing implementation, plus a student portal for guardians.
Learning, CRM and HR
A Vue 3 LMS with courses, quizzes, live classes and certificates (paid enrollments gated through Razorpay); an account-centric CRM with lead-to-deal conversion, call handling and a built-in WhatsApp API; and full HRMS with attendance, leave ledgers, payroll and appraisals.
A gateway-agnostic payments layer
A Payment Gateway registry abstracts eight providers — Razorpay, Stripe, PayPal, Braintree, GoCardless, Paytm, M-Pesa and Paymob — through one flow: Integration Request → controller verification → on_payment_authorized callback.
Key decisions
All Muneem identity — product names, logos, routes, colors — lives in one config/branding.py dictionary applied by a hook-only app at boot. App trees stay pure, so upgrades and merges can never wipe the branding.
ERPNext v16 conflicts with standalone CRM and HRMS which require Frappe v17. Instead of forcing one version, two instances run behind a unified nginx portal with shared Redis session SSO and scheduled master-data sync.
Every release migrates to staging first, runs a smoke-test loop, then backs up and migrates production. The sync runbook records every gotcha 'learned the hard way' so a release never breaks prod again.
Backend architecture
Dual-instance portal with SSO
A Frappe v16 instance (erpnext, education, lms, payments on port 8000) and a Frappe v17 instance (crm, hrms on port 9000) route through nginx by path or subdomain, with a shared Redis session layer for single sign-on and scheduled webhook/API sync for master data.
Single-server production blueprint
AWS EC2 t3.large running Ubuntu, MariaDB 11.8 with InnoDB tuning, Redis (cache :13000, queue :11000), supervisor-managed gunicorn workers, S3 for attachments and encrypted backups, and Cloudflare for DNS/CDN/WAF — with a planned Hetzner migration to cut cost.
Boot-time branding patch
The muneem_branding app overrides boot_session and extend_bootinfo to swap app titles, logos and routes per product, plus CSS/JS includes and a post-build string patcher — driven entirely by the branding config dictionary.
Payments gateway registry
Each of eight payment gateways registers itself against a common interface; payment documents flow through Integration Request → controller verification → on_payment_authorized, which updates the ERP document on authorization.
Challenges
Version conflict between ERP and CRM/HRMS
ERPNext v16 dropped the built-in HR module, and both CRM v2 and HRMS require Frappe v17 — incompatible with the v16 ERP stack. The multi-instance architecture document was written specifically to solve this, and the route guide documents the legacy Module Def confusion that resulted.
AWS free-tier can't build the platform
Free-tier-eligible instances OOM during builds, while the minimum viable production instance isn't free-eligible. The deployment plan maps a credit-burn schedule and a month-3 migration to Hetzner (~$16/mo vs ~$72/mo).
Sync and release drift across seven repos
Keeping seven app repos in sync with version branches requires a strict procedure — staging migrate and smoke test first, files backup before prod migrate, pinned payments app that must never be re-synced, and OOM-prone frontend builds needing raised Node heap.
What I learned
A hook-based overlay beats forking: merges can never wipe branding, and upgrades stay near-conflict-free.
One source of truth — a branding config dictionary plus a tracked module inventory with explicit automation gaps — makes a huge platform handover-able.
Staging-first discipline with a copy-paste smoke-test loop prevents production breakage.
Code snippets
def patch_bootinfo(bootinfo):
app_map = {info["app_name"]: info for info in bootinfo.app_data}
for internal_name, product in BRANDING["products"].items():
if internal_name in app_map:
entry = app_map[internal_name]
entry["app_title"] = product["name"]
if product.get("icon"):
entry["app_logo_url"] = product["icon"]
entry["app_route"] = product.get("route", entry.get("app_route", ""))