Smew AI Agency
Full Stack Engineer
August 2026 – Present
Background
At Smew I work on FOHE, the platform behind a community of about 6,500 higher education professionals organized into local chapters that run their own events. It handles the whole member lifecycle: applications and admissions, the member directory, chapters, events, email, and the Slack workspace where the community actually lives.
The platform is mid-migration. It grew up as Google Sheets, Apps Script and Make.com automations, and is moving onto Postgres on Supabase with a Next.js app in front. Both worlds run at the same time, some flows still round-tripping through Make and others already cut over to native webhooks, so most of the design is about managing that transition safely rather than building from scratch.
Next.js, React and TypeScript on the frontend, with React Query for server state and React Hook Form with Zod for forms. Drizzle over Postgres on the backend, Server Actions as the main mutation path, and API routes for webhooks, cron and queue callbacks. Postmark carries all email in and out, and background work runs on Vercel Queues and Cron. I work across all of it, from the schema up through the admin screens.
Migrating without a cutover day
Each Make scenario that gets replaced is something members depend on today: an admissions decision, a form submission, a welcome email. So nothing is switched over in one step. The native webhook ships next to the old scenario behind a flag, writes to the database and logs every action to a sync table, but does not send email or fire anything downstream. During that observation window the two paths can be compared on real traffic, and only once they agree does the flag flip for that one surface. Ship the new path silently, prove it, then replace.
The same instinct applies to load. On serverless, every instance holds its own connection pool, and instances times pool size can blow past the database pooler's ceiling under a burst, which is how a past site-wide outage happened. The pool is now deliberately small with aggressive timeouts, so a query under pressure fails fast instead of queuing until everything stalls.
Admissions
Applications come in through Typeform and move across a drag-and-drop kanban that admins work all day. An AI evaluation scores each one against five criteria, and that model sits outside the app on purpose: Make runs it and writes the verdict back through a webhook, and the app only stores and displays it. Re-evaluations after a clarification reply are appended to a history rather than overwriting the previous verdict, and every status change and email sent is kept as an audit trail, so a decision can always be explained after the fact.
When an application is unclear, the applicant gets a clarification email. It used to be written from the model's own question text, and it now uses fixed copy instead, so model output never goes straight into a stranger's inbox. The model decides that a question should be asked, not what the community says to someone.
Data that disagrees with itself
Member records come from several sources that do not agree. Where someone says they live and where LinkedIn says they work differ often enough that they are stored as separate fields and normalized against reference city and country data, rather than merged into one value that is wrong half the time. Email preferences are granular, so opting out of marketing does not opt someone out of coffee chats, and the link in every email carries a hashed, capability-scoped token rather than anything that identifies the member. Deletion requests run through a scheduled job, so a request to be forgotten is a guarantee, not a ticket.
Events across channels
A chapter event goes out three ways: a Slack post, a calendar invite email and a public Eventbrite listing, each written for a different reader. So an event carries a separate description per channel, each one required only when that channel is turned on, alongside recurrence rules, invites scoped across several chapters, and sync status with Eventbrite. One event, three audiences, and none of them gets copy written for someone else.
