A small help portal for foreigners living in Korea. Users submit requests; you manage them and follow up by email.
- Landing page (
/) ? intro and link to the request form - Request form (
/request) ? name, email, phone, language, category, message - Submit API — saves to Supabase and sends a confirmation email (via Brevo or Outlook)
- Admin (
/admin) — list of requests (protected byADMIN_SECRET)
The app is built to keep user data safe and to avoid common abuse:
- Admin access — Admin APIs accept only the
x-admin-secretheader (never the URL). The secret is not linked anywhere on the public site. - Input validation — All submitted fields have maximum lengths; email format is validated. Request body size is capped (~50KB).
- Rate limiting — Submit endpoint is limited to 5 requests per IP per 15 minutes to reduce spam and abuse.
- Security headers — Responses send
X-Content-Type-Options,X-Frame-Options,Referrer-Policy, and similar headers to reduce clickjacking and sniffing. - Database — Supabase RLS is enabled; the app uses the service role only on the server for admin and for validated form submissions. No secrets or service keys are exposed to the browser.
- Errors — In production, API error messages do not expose internal details (e.g. DB errors are logged server-side only).
User data (name, email, phone, message) is only used to respond to the request and is not shared with third parties or used for marketing (as stated on the About page).
npm install- Create a project at supabase.com.
- In the SQL Editor, run the script in
supabase/schema.sql. - In Project Settings → API, copy the Project URL, anon public key, and service_role key (use service_role only in server env; never expose to the client).
If you see "SmtpClientAuthentication is disabled for the Mailbox" with Outlook, use Brevo instead (free tier, no SMTP):
- Sign up at brevo.com and go to SMTP & API → API Keys. Create an API key and copy it.
- Go to Senders & IP → Senders. Add a sender: your email (e.g. your Outlook address) and name (e.g. "Livable"). Brevo will send a verification link to that email; click it to verify.
- In
.env.localset:BREVO_API_KEY=xkeysib-your-keyFROM_EMAIL=yourname@outlook.com(the verified sender email)FROM_NAME=LivableREPLY_TO_EMAIL=yourname@outlook.com(so replies go to you)
Emails are sent via Brevo’s API; your Outlook is only used as the verified “from” address, so Outlook SMTP does not need to be enabled.
Copy .env.example to .env.local and fill in Supabase keys, Brevo (or Outlook), and ADMIN_SECRET.
Optional — Discord: To get a notification in a Discord channel when someone submits a request, create a webhook (Server Settings → Integrations → Webhooks → New Webhook), copy the URL, and set DISCORD_WEBHOOK_URL in .env.local. The message includes name, email, phone (if provided), category, and description.
Optional — Zoom (auto-create meetings): To generate a new Zoom meeting per request (with date/time from the user’s chosen slot), create a Server-to-Server OAuth app in the Zoom Marketplace, add the scope meeting:write:admin, then set ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, and ZOOM_CLIENT_SECRET in .env.local. In admin, use Create Zoom meeting to create the meeting and save the join link. Optionally set ZOOM_USER_ID (default me) and APP_TIMEZONE (default Asia/Seoul).
Optional — Meeting reminder (30 min before Zoom): The app can send an automated reminder email before each meeting. You need to add a DB column, set CRON_SECRET (and optionally APP_TIMEZONE), and schedule an external cron to call the endpoint every 10–15 minutes with a secret header. See docs/CRON-MEETING-REMINDERS.md for step-by-step instructions (Supabase, env vars, cron-job.org, and Vercel).
npm run devOpen http://localhost:3000. Submit a request from /request, then open /admin and enter your ADMIN_SECRET to see it.
You don’t need an admin account to submit a request. Submissions are anonymous.
-
Submit a request (as a “user”)
Go to http://localhost:3000/request. Fill in name, email, category, message, etc. and submit. Use your own email so you receive the confirmation. Data is stored in Supabase and a confirmation email is sent (via Brevo or Outlook). -
View and resolve in Admin
Go to http://localhost:3000/admin. Enter yourADMIN_SECRETand click Load. You’ll see all requests. Click a request to open its detail page: full message, contact info, and timestamps. There you can:- Change Status (New → In progress → Resolved / Closed)
- Add Internal notes (only visible to you)
- Delete request (bottom of page): removes the request and cancels the Zoom meeting if one was created.
- Click Save to store changes in the database.
-
Verify in Supabase (optional)
In the Supabase dashboard, open Table Editor →requests. You’ll see the same rows; editing there also updates the data the app uses.
Optional — Cancel Zoom when deleting in Supabase: If you delete a request row directly in the Supabase Table Editor, the Zoom meeting is not cancelled automatically unless you set up a Database Webhook. In Supabase: Database → Webhooks → Create a new hook → choose DELETE on table public.requests, set the URL to https://your-domain.com/api/webhooks/supabase-request-deleted, add a header x-webhook-secret with the value of SUPABASE_WEBHOOK_SECRET from your env. Then when a row is deleted (from the dashboard or SQL), Supabase will call your app and the Zoom meeting will be cancelled.
To share a real URL (instead of localhost), deploy to Vercel (free tier). You’ll get a link like https://livable-xxx.vercel.app to share.
If you haven’t already:
- Create a repo at github.com/new (e.g.
Livable). - In your project folder, run:
(Replace
git init git add . git commit -m "Initial commit" git branch -M main git remote add origin https://github.com/YOUR_USERNAME/Livable.git git push -u origin main
YOUR_USERNAMEwith your GitHub username.)
-
Go to vercel.com and sign in (e.g. with GitHub).
-
Click Add New → Project and import your
Livablerepo. -
Leave Framework Preset as Next.js and Root Directory as
.. Click Deploy (first deploy may use default env). -
After the first deploy, go to Project → Settings → Environment Variables. Add the same variables you have in
.env.local(do not commit.env.localto Git):Name Value NEXT_PUBLIC_SUPABASE_URLyour Supabase project URL NEXT_PUBLIC_SUPABASE_ANON_KEYyour Supabase anon key SUPABASE_SERVICE_ROLE_KEYyour Supabase service_role key BREVO_API_KEYyour Brevo API key FROM_EMAILsender email (e.g. your Outlook) FROM_NAMELivable REPLY_TO_EMAILsame as FROM_EMAIL or your reply address ADMIN_SECRETyour secret for /admin DISCORD_WEBHOOK_URL(optional) Discord webhook for new-request notifications -
Click Save, then go to Deployments → open the ⋯ on the latest deployment → Redeploy so the new env vars are used.
Your app will be live at https://your-project-name.vercel.app. Share that link so people can open the site and submit requests. Use /admin and your ADMIN_SECRET to manage requests.
In Vercel: Project → Settings → Domains → add a domain you own (e.g. livable.example.com). Follow the DNS instructions Vercel shows.
- Add “Send follow-up email” from the request detail page (email the requester when you update status).
- Add auth for admin (e.g. Supabase Auth or NextAuth) and drop the shared secret.