Mahmoud Alshraky
I build systems people use, then keep them running. Full-stack software engineer. I build and run production systems with React, Node.js and PostgreSQL: an exam platform, an HR system for 25+ branches, a shared email gateway, and an in-browser face and hand tracking prototype. Watch each one get built, layer by layer.
How I build a system
It starts on paper
Before any code: who uses it, what must never go wrong, and what the data looks like. The plan is drawn first, then built on.
- NeuroLink began as a written spec, a test protocol and a sources file
- SQB keeps its medicine and nursing tracks strictly apart
Then the data
One database holds everything that has to be remembered, designed so a brand-new copy and the live one always match.
- SQB: 31 tables, created by a schema that is safe to re-run
- HR: PostgreSQL with migrations and indexes on the hot queries
Rules in one place
Every request passes through one core that applies the rules once, so the website, the app and the bot cannot disagree. A request that breaks a rule is refused.
- SQB: 150+ route handlers behind one API
- The web app, the Android app and the Telegram bot all use it
Everyone gets the right door
Signing in says who you are; your role says how far you get. A branch manager sees one branch, an administrator sees them all.
- HR: role-based access across 25+ branches
- One-time codes for branch accounts
Then what people see
The interface goes on last, on top of everything underneath: the same data and rules on a laptop and on a phone, right-to-left where the users read Arabic.
- SQB and HR: right-to-left Arabic interfaces
- SQB: a web app and an Expo Android app on one backend
Then I keep it running
Launch is the middle, not the end. Scheduled jobs run on time, tests guard the parts that must not break, and dashboards show whether people find it.
- SQB: 9 scheduled jobs; 5 to 468 Google-indexed pages between 30 Aug and 16 Sep 2026
- Email gateway: 35 automated tests
Numbers (19 September 2026)
- 468 pages indexed by Google. Up from 5 on 30 August, after I prerendered the public content for crawlers. Source: Google Search Console, smle-question-bank.com, 16 Sep 2026
- 1,001 visitors in 30 days. 4,262 page views, 45% bounce rate. Source: Vercel Web Analytics, smle-question-bank.com, 30 days to 19 Sep 2026
- 734 clicks from Google search in 90 days. 13.1K impressions, average position 6.8. Source: Google Search Console, smle-question-bank.com, 17 Jun to 16 Sep 2026
Systems in production
SQB: Exam-prep platform for the Saudi SMLE, SNLE and SDLE licensing exams
13,000+ practice questions for the Saudi licensing exams in medicine, nursing and dentistry. I built the web app, API, Android app and Telegram bot, and I run it.
- 13,036 questions on 9 Oct 2026: 7,871 medical, 2,105 nursing, 3,060 dental. Many have written explanations; the recall sets do not yet.
- Three exam tracks, with content and analytics kept separate per track. The dental track went live on 7 Oct 2026.
- Prerendered public pages took Google-indexed pages from 5 to 468.
It starts with a search
Students find SQB on Google. Its public pages are prerendered, so search engines read real content. Every lit tile is a page Google knows.
- Indexed pages went from 5 to 468 between 30 Aug and 16 Sep 2026
- 734 clicks from Google search in 90 days
Then they practise
Practice questions, explanations and a plan for each student, in a right-to-left interface built for Arabic readers.
- 1,001 visitors and 4,262 page views in the 30 days to 19 Sep 2026
- Live at smle-question-bank.com
Then they subscribe
When a student pays, Moyasar confirms it, the subscription switches on and a VAT invoice is generated as a PDF. The receipt email goes out through the gateway I built for my projects.
- Five plans: monthly, four-month, annual, and group plans for 3 and 5 seats
- Webhook-driven activation, VAT invoice as a PDF
And they come back
The website, a mobile app and a Telegram bot all run on one backend. A daily question and reminders bring students back.
- Web, the Expo Android app and the Telegram bot share one API
- Telegram gets a daily question and a weekly summary
- Data: DB PostgreSQL, 31 tables; TABLE user_question_progress; TABLE payment_events; TABLE funnel_events, page_engagement, login_history; TABLE subscription_groups, group_seats, trial_grants; SCHEMA Created idempotently at cold start, safe to re-run
- Search: BUILD Public pages prerendered so crawlers read real content; GSC 468 indexed pages on 16 Sep 2026, from 5 on 30 Aug; GSC 734 clicks and 13.1K impressions in 90 days
- Interface: UI React 19 and Vite, right-to-left Arabic; URL smle-question-bank.com
- Payments: PAY Moyasar: Apple Pay, webhooks; POST /webhook confirms and activates the subscription; GET /invoice/:gatewayRef.pdf (VAT invoice); MAIL Shared email gateway, Gmail SMTP fallback
- Engine: API One Express app, 150 route handlers; GET /api/questions; GET /api/questions/:id/explanation; GET /api/user-subscription/:userId; GET /admin/analytics; APP Expo SDK 57 Android app, same backend (rebuilt Oct 2026, 90 unit tests); BOT Telegram: daily question, weekly summary; CRON 9 scheduled endpoints (Telegram, lifecycle and daily emails)
React 19, Vite, Express, PostgreSQL, Moyasar, Expo SDK 57, Telegram Bot API
https://www.smle-question-bank.com
Multi-branch HR platform: Employee records, payroll, absence and documents for a healthcare company
A healthcare company with 25+ branches and 600+ employees ran its staff on paper. I built its system from scratch: people, payroll, documents, buses and reports.
- Document expiry alerts fire 30, 60 and 90 days ahead.
- JWT sessions, role-based access across branches, one-time codes for branch accounts.
- In-memory caching with short expiry and composite indexes on the hot queries.
25+ branches, one system
Every branch reports to one system: one set of employee records, one set of rules, and the buses that carry the students.
- 25+ branches and 600+ employees at a healthcare company
- Student bus transport tracked alongside staff
Nothing expires unnoticed
Every employee file carries identity, residency and contract dates, in Gregorian and Hijri. The expiry page counts what is expired, due in 30 days and due in 90, and lets head office remind a branch. Alerts fire 90, 60 and 30 days ahead.
- Alerts at 90, 60 and 30 days
- Screen shows demo data: every name and number is invented
Every branch, every year
Each new school year every branch has to review its staff, documents and buses. One page shows which branches finished, which started and which have not, and what exactly is still missing in each.
- Per-branch progress, missing documents and missing employee data
- Reminders and tasks sent to a branch from the same screen
How many staff does a branch need?
From the number of beneficiaries and the therapies each one gets, the system works out how many therapists, nurses and support staff a branch needs, and shows the gap against who is actually employed.
- Required, present and missing, per role and per branch
Payroll and absence, month by month
Attendance and absences add up to each month's payroll, per employee and per branch, with Hijri dates where people expect them.
- Payroll, absence and year transitions in one place
Reports people can print
Branch statistics rank every branch by how complete and active its data is, and export to Excel. Payroll, absence and branch reports come out as PDF and Excel files, ready to print, sign or send on.
- PDF and Excel, with Hijri dates
- Caching and composite indexes keep them fast
- Access: AUTH JWT sessions; ROLES Role-based access across 25+ branches; OTP One-time codes for branch and user accounts; AUDIT Review confirmations and year transitions
- Branches: MODULE branches, branch-documents, branch-statistics; MODULE bus-transportation; PAGE Dashboard and branch statistics; API 18 route modules, one per area
- Expiry: MODULE employees, employee-file, employee-expiry; MODULE documents, archive, notifications; ALERT 30, 60 and 90 days before expiry
- Follow-up: PAGE Year-cycle follow-up; MODULE year-cycle, tasks, terms, academic-years; TASK Head office assigns a task to a branch, with a deadline
- Staffing: PAGE Beneficiaries and hiring requirements; RULE Staff required per role from the enrolled beneficiaries; MODULE beneficiaries, branch-statistics
- Payroll: MODULE payroll-absences, academic-years; PAGE Payroll and absences
- Reports: DB PostgreSQL with migrations; CACHE In-memory cache with per-route expiry; INDEX Composite indexes on hot queries; REPORT PDF and Excel, Hijri dates
React 19, Node.js, Express, PostgreSQL, JWT, ExcelJS, PDFKit
Internal system with employee data, so there is no public link. The screens on this page are the same code running on invented demo data. I walk through the real one in an interview or call.
NeuroLink: A camera that reads faces and hands, and does not diagnose
A child plays a 3-minute game while the webcam measures behaviour, all in the browser. The doctor sees only what is new, and decides.
Built for Let's Pitch It 2026 (GDG Qassim). It is a prototype, not a medical device, and it has not been clinically validated.
- Face and hand tracking run in the browser; the video never leaves the device.
- Build checks fail if anything but one file touches the camera, or if the code names a condition.
It starts with a webcam
A child plays a short game while the webcam watches. The video never leaves the device: face and hand tracking run in the browser.
- Runs on the device with MediaPipe
- A build check fails if anything except one file touches the camera
Hands: 21 points each
Every frame the browser finds 21 landmarks on the hand, enough to tell an open hand from a point, a wave or a fist.
- Pointing and waving are checked against what the game asked for
Faces: 478 points
478 landmarks, including both irises, give the head turn, where the child is looking, blinks and expression, all condensed into one small record per frame.
- Head pose, gaze region, blinks, smile and brow raise
A game built as an instrument
Six timed blocks, 3 minutes 5 seconds in total. Every measure is tied to a timestamped moment, so sessions stay comparable with each other.
- The social-attention block approximates a published paradigm with a webcam and does not claim the original's validation
The doctor decides
The physician sees only what today's session showed that the record does not already contain, as one notification with three choices: add to the record, request a referral, or dismiss. It never diagnoses.
- Every number shows where it came from
- A build check fails if the code contains condition-naming vocabulary
- Camera: CAM One getUserMedia call site; RUN MediaPipe WASM, on the device; FALLBACK GPU first, then CPU; PIN Model files pinned to MediaPipe 0.10.18
- Hands: HAND 21 landmarks per hand; HANDS extended[5], isPoint, bearing; STATE pointing, onTask, distanceCm
- Face: FACE 478 landmarks with irises; HEAD headYaw, headPitch, headRoll; GAZE gazeRegion, gazeConf; EYES blinkLeft, blinkRight, eyesOpen; FACE jawOpen, smile, browRaise
- The game: B0 calib, 20 s; B1 social, 40 s; B2 name, 30 s; B3 point, 40 s; B4 wave, 25 s; B5 bubbles, 30 s
- The doctor: API FastAPI and SQLite engine; NORMS norms_v1.json, age norms; RULE Each field records its provenance; ACT Add to record, Request referral, Dismiss; NEVER Diagnose or name a condition
TypeScript, MediaPipe, Python, FastAPI, SQLite
The repository is private. I run it live in an interview or a call.
Shared email gateway: One email service for my projects
SQB, the HR system and this portfolio are wired to one email service on a free plan of 100 a day. The gateway splits the quota, adds a second sending account as backup, and falls back to Gmail.
- Bounces and complaints are suppressed through a webhook signed by Svix.
- Idempotency keys prevent duplicate sends; error alerts have a cooldown.
- 35 automated tests in four files.
Three projects, one door
Every project sends through the same gateway instead of carrying its own mail setup. It checks where each request comes from, and turns away mail from a source it does not know.
- The portfolio sends through it today; SQB and the HR system are connected and being moved over
- Requests from unregistered origins are dropped
A fair share of 100 a day
Resend's free plan allows 100 emails a day. The gateway splits them between the projects. When that is spent, a second Resend account takes over, and after that mail goes out through Gmail instead of failing.
- A second sending lane was tested in October: its first domain was too young and landed in spam, an older domain reached the inbox
- Gmail SMTP takes over when both quotas run out
A bounce is remembered
When an address bounces or complains, a signed report comes back and the gateway stops mailing that address. The same message sent twice goes out once.
- Webhook reports are verified with Svix signatures
- Idempotency keys stop duplicate sends
35 tests hold it together
The client, the origin check, the quota and the email templates are covered by 35 automated tests, so one change cannot quietly break mail for every project at once.
- One door: POST /api/v1/send; POST /api/v1/send/bulk; POST /api/v1/notify; GET /api/v1/quota; GET /api/v1/messages/:id
- Quota: QUOTA Splits Resend's 100 a day across projects; LANE 2 A second Resend account takes over near the first one's limit (since 9 Oct 2026); FALLBACK Gmail SMTP when both are spent
- Bounces: HOOK POST /api/webhooks/resend; SIG Signed by Svix; RULE Bounces and complaints suppress the address; SAFE Idempotency keys stop duplicate sends; UNSUB GET /u/:token
- Tests: TEST client.test.ts, 8; TEST origin.test.ts, 6; TEST quota.test.ts, 11; TEST render.test.ts, 10
Next.js, TypeScript, PostgreSQL, Resend, Nodemailer, Svix
Internal infrastructure. Its admin panel shows real users' data, so there is no public link.
Client sites
Al-Haysoni Law Firm: Website for a law firm
A bilingual Arabic and English site for a law firm: right-to-left by default, one-tap WhatsApp enquiries for each practice area, the office on a map, and structured data for search.
- React and Vite, with all Arabic and English copy in one translations file.
- Schema.org structured data, sitemap and robots.txt; I fixed the canonical domain and added visitor analytics in October 2026.
React, Vite, Schema.org, Vercel Analytics
https://www.alhisony.com
Erth Environmental Services: Website for an environmental consultancy
An Arabic site for an environmental consultancy, opening on an interactive 3D globe, with dark and light themes and a consultation form.
- Interactive Three.js globe with Saudi Arabia marked out.
- A fingerprint drawn from a mathematical field, not by hand.
Three.js, Vite, JavaScript, Google Maps
https://erthfc.com
Experience
Full-stack software engineer, Ultimate Care Rehabilitation Co.
Oct 2025 to now
- Designed and built an HR system from scratch for a company with 25+ branches and 600+ employees: lifecycle, payroll, absence, documents and student transport.
- Automated document-expiry alerts and PDF and Excel reports with Hijri dates.
Full-stack developer, SQB
Feb 2025 to now
- Built the platform, API, database, mobile app and Telegram bot, and run them in production.
- Prerendered public content for crawlers: Google-indexed pages rose from 5 to 468.
Web developer (freelance), Al-Haysoni Law Firm
Jul to Aug 2025
- Delivered a bilingual Arabic and English site with Schema.org structured data and WhatsApp enquiries.
Web developer (freelance), Erth Environmental Services
2025, redesigned Sep 2026
- Built the site, then redesigned it around an interactive Three.js globe.
About
I build the whole system, from the database to the screen, and I stay on after launch.
Contact
alshraky3@gmail.com, GitHub, LinkedIn