formgate: A Contact Form Gateway, Part 2: The Pipeline

Part of the formgate series. What formgate is around 1300 lines of Go across nine files, nothing outside the standard library. Part 1 covered the design decisions: why the emailed OTP had to go, why the sender and recipient come only from config, and how each pipeline step picks a failure direction. This part is the implementation. The whole pipeline lives in one HTTP handler, and the failure directions from Part 1 show up as concrete branches inside it. ...

September 6, 2026 · 6 min · 1201 words · Sagar Nayak

formgate: A Contact Form Gateway, Part 1: Bots and Relays

What formgate is a single Go binary, nothing outside the standard library, one config file. It sits between static marketing sites and SMTP. A public contact form posts JSON to it; formgate decides whether the submission is worth an email, and if it is, delivers it. The mail path is never exposed to the page. It serves twelve sites right now. Why The OTP that amplified spam Static sites still need contact forms, and contact forms need mail. The question is how the form sends mail without putting SMTP credentials inside a public page. ...

August 30, 2026 · 5 min · 914 words · Sagar Nayak

Running IMAP Migration Jobs in PurelyManage

Part of the PurelyManage series. The migration system in PurelyManage wraps imapsync and adds a job queue, a UI, and a few quality-of-life features on top. This post covers the job system from the user side: how jobs are created, run, monitored, and managed. The imapsync installation and the async job architecture under the hood are covered in the next post. Server Configuration Before running any migration you need to add IMAP server definitions. A server definition stores only the connection details: host, port, and whether to use SSL. Credentials are entered per-job, not per-server, so you can reuse the same server config across many migrations without re-entering connection details each time. ...

January 14, 2026 · 5 min · 944 words · Sagar Nayak

Email User Management in PurelyManage

Part of the PurelyManage series. Listing Users The Users page shows all email accounts across all your domains. The list is served from a local PostgreSQL cache rather than a live PurelyMail API call on every page load. The reason is speed and rate limits. PurelyMail’s listUser endpoint returns all users in one call, but calling it on every page load when you have 50+ users and the page auto-refreshes every 60 seconds is wasteful. The cache (purelymail_users table) is populated on first boot and kept in sync by a background cron that runs a full refresh daily at 2am. When a user is created or deleted through the panel, the cache is updated immediately so the UI reflects the change without waiting for the cron. ...

December 27, 2025 · 4 min · 678 words · Sagar Nayak

Adding and Verifying Domains in PurelyManage

Part of the PurelyManage series. Adding a Domain Adding a domain in PurelyManage is a single form: enter the domain name and submit. The backend calls the PurelyMail addDomain API and the domain appears in your list. await pmPost('/api/v0/addDomain', { domainName }) The domain will show as unhealthy immediately because no DNS records are set yet. That is expected. The next steps are ownership verification and DNS setup. Ownership Verification PurelyMail requires you to prove you control the domain before it will route email for it. Verification is done by adding a TXT record to your DNS with a value PurelyMail provides. ...

December 21, 2025 · 4 min · 679 words · Sagar Nayak