# Subrato Mandal > Product Engineer at Noketa, studying for a Bachelor of Technology (B.Tech) in Computer Science and Engineering at Adamas University. Based in Kolkata, India. Machine-readable resume (JSON Resume schema): https://subratomandal.com/resume.json Resume (PDF): https://subratomandal.com/Subrato_Mandal_Resume.pdf ## Contact - Email: subratomandalme@gmail.com - GitHub: https://github.com/subratomandal - LinkedIn: https://www.linkedin.com/in/subratomandal/ - X: https://x.com/subratomandalme - Hugging Face: https://huggingface.co/subratomandalme - LeetCode: https://leetcode.com/u/subratomandalme/ - HackerRank: https://www.hackerrank.com/profile/subratomandalme ## Experience ### Product Engineer, Noketa (Jun 2026 to present) Noketa is an email platform spanning campaigns, automations and a shared inbox. Built the architecture of its AI email editor. - One typed email document and a single bounded patch protocol that people, deterministic code and the model all edit through. - JEV, a deterministic intent and template/theme decision engine that ranks options so most edits never call a model. - A model gateway that caps each action at three calls and budgets the worst-case cost before calling. - A transactional render, compatibility-validation and repair pipeline: every change applies fully or not at all. - Fact grounding for prices, codes, dates, links and claims, plus operation-level reconciliation against concurrent edits. - Metered image generation with reserved usage and refunds. Stack: TypeScript, React, TanStack Start, Hono, Drizzle, Postgres, Redis, Bun, Turborepo, AWS ## Education - Bachelor of Technology (B.Tech), Computer Science and Engineering: Adamas University (Aug 2023 to Jun 2027) - High School Diploma, Physics, Chemistry, Mathematics, Biology: Army Public School Barrackpore (Jun 2021 to Apr 2023) ## Skills - Languages: TypeScript, JavaScript, C++, Python, Go, Rust, SQL - Frontend: React, Next.js, TanStack Start, Tailwind CSS, Vite - Backend and databases: Node.js, Bun, Hono, Express, Postgres, MongoDB, SQLite, Redis, REST APIs - Infrastructure: AWS (SES, S3, Lambda), Cloudflare, Docker, Vercel, Railway, Git, Turborepo - Machine learning: PyTorch, Hugging Face Transformers, llama.cpp, ONNX, LLM application design - Desktop: Tauri, Rust ## Achievements - S3 Explorer Railway template: Published S3 Explorer as a one-click Railway deployment template: 66 deployed projects at a 96% deploy success rate, cited by Railway developer relations as the most practical solution for the platform. ## Projects ### S3 Explorer > A secure, self-hosted file manager for any S3-compatible storage bucket. - URL: https://subratomandal.com/projects/s3explorer - Year: 2026 - Tech: TypeScript, React, Express, SQLite - GitHub: https://github.com/subratomandal/s3explorer S3 Explorer is a self-hosted web interface for any S3-compatible storage: AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Google Cloud and Railway Buckets. I built it because managing buckets meant hopping between provider dashboards and CLI tools, each with its own quirks. I wanted one interface that behaved the same everywhere, and that I could trust with production credentials. _Screenshot: S3 Explorer file manager interface_ #### Why Every provider ships its own console, and they have little in common. AWS's is dense, MinIO's is bare, R2's lives inside a larger dashboard, and a self-hosted MinIO often needs extra setup before it has a usable file manager at all. None of them keep credentials for several providers in one place. The CLI is powerful but slow for browsing, and keys left in shell history are a security risk. #### What it does It's a single Node and React app you host yourself. You sign in once with a master password, then add up to 100 bucket connections from any S3-compatible provider and switch between them in one click. The server encrypts credentials with AES-256-GCM, and nothing sensitive is ever stored in the browser; the server decrypts a key only at the moment it makes an S3 call. _Screenshot: Connection manager screen_ Files can be dragged in to upload, sorted into folders, renamed, previewed in the browser, and deleted one at a time or in batches. Downloads go through the server, so no presigned URLs leak from the page. Most things have a shortcut: - `Cmd K` opens the command palette - `Cmd ,` opens the connection manager - `Cmd U` starts an upload - `Esc` closes whatever is open _Screenshot: Command palette with actions, connections, and buckets_ #### How it's built The frontend is React and Tailwind, built with Vite. It has the file manager, the connection manager, the command palette and the upload modal, and keeps no state between sessions. The backend is an Express server in TypeScript. It handles sign-in, sessions and encryption, and proxies every S3 operation through the server-side SDK, so the browser never sees a raw key. SQLite, through better-sqlite3, holds the password hashes, sessions and encrypted connection records. That keeps the deployment self-contained, with no external database to run. Passwords are hashed with Argon2id, and sessions live on the server behind httpOnly, secure, SameSite=Strict cookies. It ships as a Docker image with a one-click Railway deploy, and a persistent volume at `/data` holds the database. #### What I learned **Don't trust the browser with credentials.** The first version kept keys in the browser and signed requests there, so a malicious extension or an XSS bug could steal production keys. Moving every SDK call to the server cost one extra network hop and removed that whole class of attack. **Plan the key's lifecycle.** AES-256-GCM needs a key that survives restarts, but I didn't want a hard-coded key or a separate KMS to manage. The key is derived from a `SESSION_SECRET` kept in the host's secret store. Rotating it invalidates every stored credential, which is exactly what should happen after a leak. **SQLite was the right size.** A single-user tool needs no daemon, no network config and nothing to operate. better-sqlite3 is synchronous, but session lookups and connection reads all finish in under a millisecond. Postgres would have been over-engineering. **Rate-limit by address, not session.** My first limiter was per session, which is useless because an attacker never has one. Ten failed attempts from one IP in fifteen minutes now locks it out for thirty, and with Argon2id's deliberate slowness, brute-forcing the master password is impractical. ### Epito > A private, local-first desktop notes app with on-device AI that works entirely offline. - URL: https://subratomandal.com/projects/epito - Year: 2026 - Tech: TypeScript, Next.js, Tauri, Rust, llama.cpp, SQLite - GitHub: https://github.com/subratomandal/epito Epito is a desktop note-taking app with a language model built in. You write in a rich editor, your notes live in an encrypted SQLite database on your own machine, and the AI features run on a llama.cpp model that only starts when you ask for it. Nothing leaves the device. It ships for macOS, Windows and Linux. I built it because every notes app that added AI also added a cloud dependency. I wanted something private, a place I'd be comfortable writing journal entries, meeting notes or unfinished code. _Screenshot: Epito editor with a note open and the insights sidebar_ #### Why AI note apps mostly sync everything to a vendor's servers and run the model there. That costs you privacy. The platform can read your notes, keeps them on its infrastructure, and exposes them to its next policy change or breach. For journalists, researchers, engineers under NDA, or anyone in a sensitive job, that rules them out. Going without AI gives up real wins, though. Searching a year of notes by meaning, summarising a long document in one click, or having a dense passage explained are useful. What I objected to was AI that needs your data. #### What it does Notes save as you type, with no save button; the file on disk is always current. In the background every note is split into chunks and embedded, so search ranks results by meaning, and falls back to plain text matching while the embeddings warm up. _Screenshot: Search dialog ranking notes by similarity_ The AI panel can summarise the current note, explain a selected passage in plain language, or answer a question using the most relevant parts of all your notes as context. PDFs and images dropped into the app are read with OCR and stored, so they become searchable and usable as context too. _Screenshot: Image viewer with extracted text, topics and entities_ Notes export to PDF with proper A4 pages, to DOCX for people on Word, or to a PNG snapshot. All of it works offline. #### How it's built The interface is Next.js 14 with React 18 and TipTap, styled with Tailwind, inside a Tauri 2 shell. The Rust side owns the window, starts the Node process that runs the app logic, manages the model server, detects the GPU and its memory, handles Windows display scaling, and coordinates shutdown. App logic lives in Next.js API routes in the embedded Node runtime, covering notes, search, OCR and AI, behind a thin layer for CSRF protection and security headers. Notes, documents, images and their metadata share a single SQLite file, and the few sensitive settings are encrypted with AES-256-GCM. Embeddings come from `all-MiniLM-L6-v2`, run locally on ONNX through `@xenova/transformers`, and are matched by cosine similarity in memory. A small pipeline handles chunking, embedding, topic extraction and gathering context for chat, and a gate stops several models being started at once. The model runs as a `llama-server` process. It starts on the first AI request, streams its answer back, clears its cache afterwards, and exits after 30 idle seconds. It prefers CUDA, then Vulkan, then the CPU, and decides how much of the model to load onto the GPU from the memory available. Text extraction uses PaddleOCR, Tesseract.js or `pdf-parse`, depending on the input. Scanned PDFs are read with OCR, regular PDFs are parsed directly, and images go through Tesseract. Exports use Puppeteer-core for PDF, `html-to-docx` for Word, and html2canvas with jsPDF for images. #### What I learned **Local models need aggressive idle management.** The first version kept the model server running all session, which on a 7B model pinned more than 5 GB of video memory while you were only typing. Starting it on demand and stopping it after 30 idle seconds gives that memory straight back. The first answer after a pause takes a couple of seconds longer, which is a fair trade. **Tauri and Next.js work well together.** Rust handles the native concerns, like GPU detection, processes and shutdown, and the interface keeps modern React tooling in a webview. The app is about 20 to 40 MB, against 150 MB or more for the same thing in Electron. **Simple signals beat clever IPC.** Rust must not exit while Node is still writing a note. Of the approaches I tried, the simplest was the most reliable. Node writes a small file when it wants to exit, and Rust checks for it every 200 ms and acknowledges. It survives either side crashing, and it's trivial to debug. **One person's notes fit in memory.** I designed around a disk-backed vector database, then measured. Thousands of notes fit in memory, and searching all of them takes a few milliseconds. I replaced the database with a plain array. Fewer dependencies, faster startup. **GPU detection differs on every platform.** CUDA on Linux is easy and Vulkan is harder, but Windows, with its power-saving settings, needs system APIs called from Rust; part of `native_win.rs` exists only to make Windows behave. On a Mac, Metal is always available. ### Dyeink > A quiet, fast blogging platform for people who just want to write. - URL: https://subratomandal.com/projects/dyeink - Year: 2025 - Tech: TypeScript, React, Tailwind CSS, Supabase, Postgres, Cloudflare Pages - GitHub: https://github.com/subratomandal/dyeink Dyeink is a minimal blogging platform for people who just want to write. I built it because publishing tools had grown heavy. You open one to write a paragraph and get toolbars, settings and prompts to share it in seven places. Dyeink has none of that. You open it, write, and publish. _Screenshot: Dyeink editor_ #### Why Each blogging platform wants something from you besides writing. Substack pushes you to grow a mailing list, Medium adds engagement widgets, and WordPress is powerful but hands you plugins, themes and updates to look after. For someone who wants a small, calm place to publish, closer to a notebook, none of them fit. Building your own site instead turns into one more project that keeps you from writing. #### What it does Dyeink gives you a quiet editor and a clean reading view, and keeps the infrastructure out of sight. You sign in, write without distractions, and publish in one click. It works down to phone size, and you can connect your own domain so the blog lives at your address, not someone else's. _Screenshot: Reading view in dark mode_ Writing is markdown-first, with keyboard shortcuts for the common cases and no toolbar floating over your cursor. Drafts save every few keystrokes, so nothing is lost. The reading view uses generous line height and a column of about 65 characters, which stays comfortable past the first paragraph. Connecting a domain takes a CNAME record and one entry in the dashboard, and the blog is live on it within minutes. #### How it's built The frontend is React and TypeScript, built with Vite, routed with React Router, and styled with Tailwind on a handful of spacing and color tokens. Client state lives in Zustand; the store is only a couple of slices for sign-in and drafts, and Zustand needs none of Redux's boilerplate. Post types are shared end to end, from the database row to the editor. Supabase provides the Postgres database, sign-in and Row Level Security. Each post row holds its author, slug, title, content, status and publish date, and a unique index on author and slug keeps every author's URLs stable. Policies let authors read only their own drafts, while anyone can read what's published. The site is hosted on Cloudflare Pages, and there's a Docker image for anyone who'd rather host it themselves. _Screenshot: List of posts_ #### What I learned **Let the database enforce access.** My first version checked ownership in every handler, which works until one handler forgets. With Row Level Security the database itself refuses to return rows a user shouldn't see, for every query, including ones I haven't written yet. Each policy is a few lines. **I said no to most features.** Every button and suggestion is something the writer has to ignore to keep going. A feature shipped only if it helped someone write faster or better. **Custom domains need a little routing.** The host issues certificates through its domain API, but each request still has to reach the right blog. A small edge function reads the incoming host, looks up its owner and rewrites the path to that author's blog, with nothing unusual needed in the writer's DNS. _Screenshot: Dyeink on a phone_ ### DiabetesSLM > A small language model fine-tuned to answer questions about diabetes. - URL: https://subratomandal.com/projects/diabetesslm - Year: 2023 - Tech: Python, PyTorch, Flask, Hugging Face - Live demo: https://huggingface.co/spaces/subratomandalme/diabetes - GitHub: https://github.com/subratomandal/diabetes DiabetesSLM is a small language model fine-tuned to answer questions about diabetes, served through a simple web interface on Hugging Face Spaces. I wanted to know whether a fine-tuned Flan-T5-large could answer diabetes questions reliably, without the cost or latency of a frontier model. > This is an educational demo, not medical advice. Anyone with a real health concern should talk to a qualified professional. _Screenshot: DiabetesSLM question and answer interface_ #### Why General-purpose models are capable but awkward for narrow jobs. They're expensive to run, they can be confidently wrong outside what they've seen, and depending on an external API makes self-hosting and privacy-sensitive uses hard. In a focused domain most of a large model goes unused, so a smaller, tuned model should give the same answers for a fraction of the compute. #### What it does I fine-tuned Google's Flan-T5-large on question and answer pairs built from the NHS's diabetes content. It answers common questions about symptoms, management, diet and complications, runs comfortably on modest hardware, and usually responds in a few hundred milliseconds. _Screenshot: The model answering a question_ A question typed into the chat goes to `app.py`, which hands it to `model_runner.py`. That cleans up and tokenises the input, the model generates an answer, and the decoded text comes back to the chat window. #### How it's built It's Python throughout, on PyTorch, using `transformers` to load the model, `datasets` to turn the CSV into a training set, `sentencepiece` for tokenisation, and `accelerate` to train on a single GPU. Flask serves the API and Gradio provides the chat interface, which meant writing no frontend at all. The Space rebuilds from the Git repository on every push and serves the model on a free CPU instance. The weights are stored with Git LFS, and a Dockerfile is included for running it on your own hardware. The training script, dataset and inference code are all in the repository, so anyone can fine-tune their own version in a few hours. _Screenshot: A sample answer_ #### What I learned **Small models hold up in narrow domains.** Flan-T5-large has 780 million parameters, a fraction of a GPT-class model, yet after fine-tuning it's competitive at this one task. Ask it about anything else and it confidently answers nonsense, so the domain has to stay narrow. **Consistent data beats more data.** The training set is a few thousand pairs, all from one source, in one voice, with one structure, and that consistency is what the model learned. Adding data from varied sources made the answers worse. **Spaces is a good home for demos.** Push to Git, it builds, and you get a public URL, with no servers, no scaling config and no card on file. The free CPU tier is slow for large models but fine at this size. **Be honest about the limits.** The model hallucinates, knows nothing of recent research, and struggles with compound or ambiguous questions. Rather than hide that, the README documents it and the interface opens with a clear disclaimer. A small model pretending to be a doctor is the worst way this could fail. ## Site - Letter (home): https://subratomandal.com/ - Projects: https://subratomandal.com/projects - Experience: https://subratomandal.com/experience - Contact: https://subratomandal.com/contact - Blog: https://blog.subratomandal.com