Project Story Inspiration

Origin: I wanted a small, practical tool that lowers friction between residents and city staff — an app that turns short citizen reports into clear, actionable emails.

Why it mattered: Many civic requests fail because of poor formatting, missing details, or misrouting. A lightweight interface that drafts department-directed emails and lets users edit before sending reduces that waste.

What I Built

Stack:

Backend: Node.js + Express

Frontend: Vanilla HTML, CSS, JavaScript

Key files:

server.js — routing, email generation, send/save fallback

index.html — UI and modal

script.js — form submit, preview/send flows

styles.css — styling

API.env — runtime secrets

Core features:

Draft generation: via generateEmailContent (AI or generic template).

Preview/edit modal: UI lets users adjust subject/body before sending (/mcp/preview + /mcp/send-custom).

Safe fallback: if SMTP fails or isn’t configured, the server writes test-email-.txt so no reports are lost.

How I Built It

Routing: Implemented REST endpoints:

POST /report — classification and routing logic (heuristics + optional AI).

POST /mcp/preview — returns generated subject/body for review.

POST /mcp/send-custom — accepts exact {to, subject, body} and uses sendOrSaveEmail.

Email transport: Used Nodemailer with SMTP credentials from API.env. The send function attempts SMTP first, then falls back to saving locally.

UX flow:

User enters issue + location.

Generated preview appears.

Editable modal opens.

Edited content is sent via POST /mcp/send-custom.

Small, careful changes: Added a header image and a .header-icon rule without altering layout or behavior.

What I Learned

Operational lessons: Environment variables must be set before the Node process starts. Editing API.env is insufficient unless you restart or export the session vars. On Windows, killing a port-bound process often requires elevation.

Email lessons: Gmail SMTP rejects credentials (535 BadCredentials) without 2FA + app password. Transactional providers (SendGrid, Mailgun) are far more reliable for production.

Product lessons: Allowing user edits is essential. If you regenerate AI content, it overwrites edits unless you explicitly send the edited {subject, body} to /mcp/send-custom.

Challenges & How They Were Solved

SMTP authentication

Issue: Nodemailer failed with 535 errors until 2FA/app password was set.

Mitigation: Use robust fallback (save-to-disk), prefer session env vars, and consider SendGrid for production.

Windows port handling

Issue: EADDRINUSE and permission errors restarting Node.

Mitigation: Use Get-NetTCPConnection + Stop-Process carefully with admin privileges.

Preserving user edits

Issue: UI initially re-generated emails, overwriting user edits.

Fix: Added /mcp/send-custom endpoint and updated client to send edited {to, subject, body} directly..

Share this project:

Updates