DEV Community

Cici Yu for Momen

Posted on Originally published at momen.app

Astro K Joseph Built an AI Interior Redesign App That Emails You Four Costed Concepts

Astro K Joseph built a full-stack interior redesign app by pairing Claude Code with Momen. A user uploads one photo of a room and gets back four redesigned versions of that same room in four different styles, each with a renovation cost breakdown, and the whole report arrives in their inbox. Momen runs the backend — database, logins, permissions, AI agents, server-side logic, email integration — and Claude Code builds the React frontend on top of it.

He never opened the Momen editor to configure any of it. Momen's no-code plugin connects Claude Code to a Momen account, so one detailed prompt describing the app produced the tables, the agents, the Actionflows and the third-party integration directly inside the project. The finished build is public — you can open it in the Momen editor and clone it into your own workspace.

What the app does

  • Upload a room photo — drag in a file, or paste a link to an image on the web and let the server fetch it
  • Describe the job — room type, budget, currency, and free-text notes
  • Four concepts, not four variations — an AI agent reads the photo and returns four genuinely distinct interior design styles, each with a palette, materials, lighting and signature pieces
  • Photorealistic renders that keep your room — each concept is rendered from the original photograph, with camera angle, wall positions, window and door placement and ceiling height held fixed
  • Costed per concept — furniture, materials and finishes, labour, and other/contingency, plus a total and notes on where to economise
  • A report by email — once every render has settled, the backend sends one email containing the original photo, all four renders, every cost table, and a closing recommendation
  • Accounts required — sign-up and login gate the whole app, and each user only ever sees their own requests

The data model: four tables the prompt created

The app runs on Momen's built-in account table plus four that Claude Code added:

  • design_request — one row per submission: customer_name, email, room_type, budget, notes, currency (defaults to USD), the uploaded original_image, and an access_token. It also carries the pipeline's state: status (defaults to Uploaded), error_message, completed_at, and three fields the AI writes back — room_analysis, structural_description and summary_recommendation. account_id is what ties a request to the person who made it.
  • generated_design — one row per concept, four per request: style_name, description, the image_prompt written for the render model, the finished design_image, a sort_order, and image_status (defaults to pending) with image_error beside it. Renders are generated in parallel, so each row tracks its own success or failure rather than the request tracking one combined state.
  • cost_estimate — furniture_cost, materials_cost, labor_cost, other_cost, total_cost, currency and breakdown_notes, linked to both the design_request and the specific generated_design it prices.
  • email_log — recipient, subject, email_type (defaults to results), status (defaults to queued), provider_message_id, error_message and sent_at. It is not just an audit trail; it is the mechanism that stops the app emailing you four times, which the Actionflow section below explains.

structural_description is the field worth pausing on. It exists because the render model receives the original photograph, not a blank canvas — so the analysis step has to write down the fixed architecture in plain language and hand it forward, otherwise each render drifts into a different room. Getting that dependency into the schema up front is the difference between a demo and something that holds together, which is the broader argument for why backend structure matters even if you don't write code.

Logins and permissions, added by a second prompt

The first version of the app had no accounts. Astro then asked Claude Code to add sign-up and login and restrict the app to authenticated users, explicitly skipping email verification for now — and the reply was that the row-level rules already keyed on account_id, so real accounts dropped straight in.

Momen's built-in authentication covers that without a separate auth provider. Username-and-password is what the app uses; email and phone sign-in are available on the same config.

Permissions are set per role, per table, per column. The project's Logged-in User role can:

  • insert a design_request only with its own account_id, and only through the columns a form should touch — name, email, room type, budget, notes, image, currency
  • read back its own design_request rows, and only those
  • read the generated_design and cost_estimate rows belonging to its own requests
  • read and update its own account row, limited to email address and profile image

The Anonymous User role holds no permissions at all on any of those four tables, which is what makes the login wall real rather than a redirect the frontend performs. email_log is readable by neither role — it is written and read exclusively by server-side logic. Momen's permissions documentation covers how the role, table and column layers stack.

Two AI agents, each with a narrow job

Both agents were configured through the plugin — model choice, system prompt, typed inputs and output schema — with no manual setup in the editor.

room_analyst runs on Gemini 3.1 Pro with image input at high quality. It takes the room photo, room type, budget text, notes and currency, and returns structured output rather than prose: a room_analysis, a structural_description, a summary_recommendation, and an array of exactly four concepts. Each concept in that array carries its own style_name, description, image_prompt and the four cost components with a total the schema requires to equal their sum. Because the shape is enforced by the schema, the Actionflow downstream can write straight into the database without parsing anything.

Its system prompt is where the product logic actually lives. It instructs the model to read the fixed architecture first and treat it as a constraint, to produce four genuinely different styles rather than four variations of one look, to write each image_prompt as a restyling instruction for a model that will see the original photo, and to keep at least two concepts at or under the stated budget while flagging any concept that deliberately exceeds it.

design_renderer runs on Gemini 3 Pro Image at temperature 0.9. It takes the room photo, the render prompt, the style name and the structural description, and returns one restyled photograph. Its system prompt restates the constraints as absolutes: identical camera position and focal length, identical wall positions and ceiling height, identical window and door placement, natural light from the same windows, and no added or removed structural elements. That is what produces the before-and-after slider effect in the finished app — the two images line up because the second one was never allowed to invent a new room.

Four Actionflows, three of them running in the background

process_design_request — asynchronous, 600-second timeout, triggered by the frontend with a design_request_id. It loads the request, marks it as analyzing, calls room_analyst, saves the analysis, structural description and recommendation back to the request, then loops over the four returned concepts and inserts a generated_design row and a cost_estimate row for each. Being asynchronous is what lets the frontend show a live "analyzing" state instead of holding a request open for minutes.

render_design_image — asynchronous, 600-second timeout, and nobody calls it. A database trigger named Render image when a design is created fires on every insert into generated_design and passes the new row's id. So the moment the loop above writes four concept rows, four render jobs start in parallel on their own. Each one loads its design row, calls design_renderer, and saves the returned image — a code step normalises the several shapes the image reference can arrive in and marks the row ready, or writes failed with an error message if no usable image came back.

The interesting part is what happens next, because four parallel jobs finishing at four different times need exactly one of them to send the email. Each job runs a claim step: it counts how many of the request's designs have settled — ready or failed — and stops if any are still outstanding. When the last one finds them all settled, it inserts a row into email_log using a unique constraint on the request-and-type pair. Whichever job wins the insert gets a non-zero affected-row count and proceeds; any that raced it get zero and stop. Only that winner marks the request as estimating costs and calls the email flow.

send_results_email — synchronous, 120-second timeout, called only by the winning render job. A code step queries the request with all its designs, images and cost estimates, sorts the concepts by sort_order, computes the average and lowest concept cost, and builds the email as inline-styled HTML: a header, the room analysis, the original photo, then one block per concept with its render, its description and a four-line cost table, followed by a comparison against the stated budget, the recommendation, and a disclaimer that the costs are AI-generated planning estimates. It then calls Resend, marks the email_log row as sent, and walks the request through email sent to completed.

import_image_from_url — synchronous, 60-second timeout. This is the "paste a link" path, and it is the one flow that is mostly defensive code. Because the server fetches a URL the user supplied, the step rejects anything that isn't plain HTTP or HTTPS, strips embedded credentials and ports before checking the hostname, blocks localhost, link-local and private address ranges so the request cannot be aimed back at internal infrastructure, downloads the file with an explicit user agent, and then verifies the stored media is actually an image rather than a PDF or an HTML error page before returning its id.

All four are built in Momen's Actionflow editor from the same node vocabulary — query, update, insert, branch, loop, AI call, third-party call, and code blocks for the parts that need real logic. What differs between them is only the trigger: a frontend call, a database insert, another flow, or a direct request.

There are no scheduled jobs and no payment integration in this project.

The one external service: Resend

The project defines a single third-party integration, resend_send_email, a POST to https://api.resend.com/emails with a typed body — from, to, subject, html and an optional reply_to — and an Authorization header. Astro created the Resend key in his own account and pasted it into the build prompt along with a link to Resend's API documentation, so Claude Code knew both the credential and the request shape it had to model.

The key lives in the Momen backend, and the frontend never sees it. The browser calls the backend; the backend calls Resend. Momen's API integration and secret management docs cover how that boundary is configured.

The frontend, and where it lives

Claude Code generated the React interface and ran it locally on the first pass, verified against the deployed backend. It reads and writes through the GraphQL API that Momen generates automatically from the data model, so there is no backend code in the frontend repository — the permission rules described above are enforced on the server regardless of what the client asks for.

Because the interface was built outside Momen, it doesn't use Momen's one-click deploy, which is the path for frontends built in the Momen editor. Astro deployed this one to Vercel and then had Claude Code run an automated end-to-end browser test suite against the live URL, which passed four of five checks and caught one broken route — a reminder that the backend being correct and the app being finished are different milestones.

What running this actually costs

The plan floor is Basic, and what sets it is counts rather than volume: the Free plan allows one Actionflow, one AI agent and one third-party API per project, and this build uses four, two and one. There are no payments in the app, so nothing forces Pro.

What actually moves the bill is AI points. Every submission is one vision call plus four image generations, and image generation is the expensive kind of inference — it dwarfs the storage and traffic lines, which stay small because the rows are tiny and the images are the only heavy objects. All three are add-ons that top up a single resource, so none of them pushes the project to a higher tier.

Momen's pricing calculator puts a version of this app at $144.83/month: $39 for Basic, $2.92 for extra object storage, $2.92 for extra outbound transfer, and $100 for ten AI-point add-on units. Those add-ons are sized against a specific scenario — roughly 4,500 registered accounts accumulated over 225 operating days, generating about twenty redesign requests a day, which works out to around 18 GB of stored renders and 57 million AI points a month. Almost seventy percent of that total is the image generation. Change how many people redesign a room and the figure moves with it, so it's worth re-running the calculator against your own numbers.

Resend bills separately from Momen and has a free tier that covers a project at this volume, and frontend hosting on Vercel is free at this scale.

Astro walks through the whole build — the master prompt, Claude Code creating the API, the agents and the Actionflows live in the editor, the first end-to-end run, the email landing in his inbox, and the later login prompt — in his video, and the project itself is open in the Momen editor to clone.

To build something along these lines: install the Momen plugin in Claude Code, and put the whole system into one prompt rather than feeding it a feature at a time — the data it collects, what each AI step should return, which external services to call and with which credentials. The backend gets built first, and the frontend is then generated against an API that already exists.

Top comments (0)