Skip to main content
All engineering work

02 / Full-stack application

2 min read

Photography Portfolio & Inquiry Application

I built and maintain a photography application that connects browsing a body of work with a reliably stored client inquiry.

Organization / project
Alan Li Photography
My role
Independent project · Design, development & maintenance
Period
Aug 2025–Present
  • Next.js
  • TypeScript
  • Turso
  • Resend

Problem

Let visitors start an inquiry without requiring a finalized session plan, and retain it even if email fails.

My contribution

Portfolio structure, the inquiry flow, server validation, durable storage, and notification recovery.

Engineering focus

A successful receipt means the inquiry was stored. It does not mean a session was booked.

Screenshot of the live Alan Li Photography homepage
Live website screenshot · September 2026. The linked site may change as the project evolves.

A real product for my photography practice

Visitors browse a series, optionally carry a visual reference into an inquiry, and start a conversation with a name and email. Dates and detailed plans can stay open.

I maintain both the photography and the application. The documented revision was developed with AI assistance; the work includes choosing the product behavior, reviewing implementation, and validating it. Next.js, libSQL, and Resend provide the underlying framework, database transactions, and email transport.

Store the inquiry before depending on email

The documented implementation validates input on the server and stores the inquiry together with pending notification state in a database transaction. A receipt represents durable storage, while email notification is tracked separately.

The handler makes a bounded initial notification attempt. Private operator tooling supports pending work and explicit retries. Accepted, failed, and uncertain outcomes remain distinct; provider acceptance alone does not establish inbox delivery.

Handle a retry as a retry

A submission carries a request ID. Database uniqueness provides duplicate protection, and normalized-content comparison distinguishes a repeated request from changed details using the same ID.

This makes uncertain responses a product concern as well as a backend concern: the interface preserves context so a visitor can understand whether they are retrying or deliberately starting a new inquiry. It is not an exactly-once email guarantee.

Give images and inquiry data separate lifecycles

A typed photo catalog and reusable series pages organize the portfolio. WebP exports and Vercel Blob handle public image delivery; Turso stores transactional inquiry and notification data.

The image delivery manifest records URLs and dimensions. This supports content-driven page generation without making a database query part of browsing the gallery. Image processing is described as an asset pipeline, not as a measured page-speed improvement.

Validation with explicit boundaries

The published engineering notes describe automated checks for malformed input, duplicate and concurrent submissions, storage failure, notification failure, and uncertain outcomes. They also document browser checks and a separately controlled email test.

These are the photography project's recorded checks, not tests rerun by this portfolio. The linked notes contain the test context and limitations; no test count or coverage percentage is claimed here.

What the application does not do

This application starts an inquiry. It does not provide payments, client accounts, live availability, or a complete booking system.

There is no automatic retry schedule in the documented implementation. Pending, failed, and uncertain notifications require operator review. Session details are confirmed separately with the client.

Next project

Bilingual Theater Website

Read case study