KiraWebs
Kirawebs.com is a marketing site for a web-development agency, built around an interactive multi-step form that lets a prospective client simulate their website's cost before ever talking to a salesperson — turning "how much would this cost?" into a self-serve answer instead of a quote request.
Architecture and Tech Stack
Core Architecture
- Framework: Next.js (App Router) + React
- UI: Tailwind CSS + shadcn/ui components
- Validation: Zod on every contact-form submission
- Rate limiting: Redis, fixed-window limiter (3 messages / minute per IP)
- Data storage: Notion API — contact submissions are written straight into a Notion database, no separate backend needed
- Deployment: Appwrite
Contact Form Flow
The whole submission path runs inside one Server Action: validate first, rate-limit second, persist third — each step can reject early with a specific, user-facing message instead of a generic error.
Key Features
Features at a Glance
Interactive Website Cost Simulator
A guided multi-step form — templates, sections, services, then a summary — walks a visitor through configuring their site and returns a live cost estimate, replacing a back-and-forth quote request with something the visitor can explore on their own.


Contact Form with Validation and Rate Limiting
Email + message are Zod-validated client- and server-side before anything is written; a Redis fixed-window limiter caps submissions at 3 per minute per IP, so the same abuse pattern that would spam a mailbox instead gets a clear "please wait" response.


Technical Highlights
Notion as the contact-form database
Instead of standing up a database for a handful of contact submissions, sendEmail writes each one straight into a Notion database via the Notion API — zero infra to run for what's genuinely a low-volume form.
Rate limiting checked before validation
The Redis fixed-window limiter runs first in the Server Action, before Zod even looks at the payload — an abusive client is turned away before the app spends any work parsing or validating its input.
Every rejection has a specific, user-facing reason
Rate-limited, invalid input, and a failed Notion write each return their own title + details pair instead of a single generic error — the visitor always knows exactly what to fix or wait for.
Project Structure
src/ ├── app/ │ ├── page.tsx # Single-page marketing site │ ├── layout.tsx │ └── actions/ │ └── contact.ts # Server Action: rate limit → Zod → Notion write ├── components/ │ ├── home/ │ │ ├── hero-section.tsx │ │ ├── services-section.tsx │ │ ├── compare-section.tsx │ │ ├── price-simulator-section.tsx │ │ ├── price-simulator/ # Multi-step cost simulator (templates → sections → services → summary) │ │ ├── steps-section.tsx │ │ ├── faq-section.tsx │ │ └── contact-section.tsx │ │ └── contact/ │ │ └── contact-form.tsx │ ├── layout/ # Header, footer │ └── ui/ # shadcn/ui primitives
Impact and Scalability
- Self-serve cost estimation: replaces manual quote requests with an instant, explorable simulator
- Zero-infra data capture: contact submissions land directly in Notion, no database to provision or maintain
- Abuse-resistant by default: Redis rate limiting protects the form without needing a separate service
- Deployed and live: running on Appwrite at kirawebs.appwrite.network
Notes
Built with Next.js, Tailwind CSS, Redis, and the Notion API, deployed on Appwrite. Code is public on GitHub.
🖼️ IMAGE PLACEHOLDER — Notion database view showing stored contact submissions