Build a Specialty Equipment Quote Request Tool With Cursor: The Qualification-Tree-First Prompt That Stops Sales From Getting Blank RFQs

Article title on dark teal hero banner with green accents, about Cursor-built equipment quote request tool.

Share This Post

TL;DR

  • Build a specialty equipment quote request tool with Cursor by writing the qualification decision tree as a typed TypeScript file FIRST, before any UI exists. UI-first prompting recreates the flat-schema Typeform problem.
  • Derive the Zod validation schema FROM the qualification tree, not parallel to it. Parallel schemas are how branching spec logic gets quietly flattened.
  • Use presigned uploads to Cloudflare R2 or AWS S3 for 8 to 40MB engineering PDFs. Post the resulting file URL into a CRM note field, never as an attachment.
  • Gate the qualified=true flag with a Next.js server action, not client-side JavaScript. Client validation can be disabled, bypassed, or fail to load in restrictive browser environments — only the server is a real gate.
  • Stack: Cursor, Next.js, TypeScript, Zod, Vercel, R2. Afternoon build. No engineer required if you follow the prompt order.

Questions this article answers:

Why Self-Serve RFQ Tools for Specialty Equipment Die in Week Two

Most self-built specialty equipment quote tools die in week two because the qualification logic got flattened into one schema. The code is fine. The shape is wrong.

The marketer ships on a Thursday. QA passes. By the next Friday, sales says the RFQs from real prospects are landing in the CRM with the wrong fields populated, and the project gets blamed on “AI tools aren’t ready.” The AI tool was fine. The prompt order was wrong.

Here is the actual mechanism. Generic form builders (Typeform, Jotform, HubSpot forms) tend to land submissions as one flat record. Voltage, throughput, duty cycle, and dimensions all sit as optional fields on the same object. The form looks branching to the buyer. The data that lands in your CRM is flat.

Your sales team’s discovery call is the opposite of flat. They ask different questions for a 480V three-phase chiller than they ask for a single-phase packaged unit. When the data shape does not match how sales qualifies, sales gets RFQs with the wrong fields populated and treats them as junk.

Cursor lets a non-engineer rebuild this the right way in an afternoon. But only if you stop Cursor from defaulting to UI-first scaffolding. The rest of this article is the exact prompt order, file structure, and validation gates that prevent the blank-RFQ failure mode.

Who this is for

This is for a marketing manager or business owner at a specialty equipment manufacturer who is already spending real money on paid media. You are getting RFQs through a tool that does not match how sales qualifies. You do not need to have written code before. You do need to be comfortable opening Cursor, pasting prompts, and pressing buttons in Vercel and Cloudflare.

If your equipment is ITAR-controlled, sold into regulated industries with audit requirements, or your qualification tree branches more than three levels deep on tacit sales knowledge nobody has written down, stop reading and hire an engineer. More on that at the end.

Why You Must Scaffold the Qualification Tree in TypeScript Before Cursor Generates a Single UI Component

The single prompt that decides whether your build works is the first one. It must forbid Cursor from generating any UI.

Cursor’s training data is full of form components. Its default behavior is to scaffold a pretty multi-step form immediately. Once that form exists, the data schema gets shaped to fit the form. That is exactly how you rebuild the Typeform problem with new paint.

The fix is to make Cursor write qualification.ts first. This file is a typed enum tree. It encodes equipment category, then the spec questions that branch from each category, using TypeScript discriminated unions.

Key Concept: A discriminated union is a type definition where one field (the discriminator, like equipmentType) determines which other fields are required. It is the opposite of a flat schema with optional fields. The TypeScript compiler enforces the branching at the type level, so a flat schema becomes impossible. This is the structure that mirrors how your sales team actually qualifies on a discovery call.

The exact first Cursor prompt

Open Cursor in an empty folder. Create a new Next.js project. Then paste this as your first prompt:

“` I am building a quote request tool for a specialty equipment manufacturer.

Before you generate any UI, any page, any component, or any form:

  1. Create a single file at /lib/qualification.ts.
  2. In that file, define a TypeScript discriminated union called

EquipmentRFQ. The discriminator is equipmentType.

  1. For each equipment type I list below, define the required spec

fields as a separate variant of the union.

  1. Use string literal enums for any field with fixed options

(voltage, phase, environmental rating, duty cycle class).

  1. Use branded number types for any field with units

(throughput_gpm, load_lbs, dimensions_in).

  1. Do not generate a Zod schema yet. Do not generate any UI.

Do not create any other files.

Equipment types and their required specs: [paste your list here, in plain English, exactly as your sales team asks them on discovery calls]

Return only qualification.ts. Wait for my approval before writing anything else. “`

The explicit “do not generate any UI” line is load-bearing. Without it, Cursor will write the form anyway and the whole point is lost.

Hand the qualification.ts file to your sales team

The second reason this prompt order matters: the resulting TypeScript file doubles as a sales-marketing alignment artifact. You do not need your AEs to read TypeScript. You read it to them, out loud, in plain English.

“For chillers we are asking voltage, phase, tons of capacity, condenser type, and environmental rating. For conveyors we are asking belt width, load per linear foot, throughput in units per hour, and duty cycle.”

The sales team will catch missing fields in fifteen minutes. They will tell you that for chillers you also need ambient temperature range, and that asking phase before voltage is backwards from how they ask it on the phone. You change qualification.ts and re-read it. Once they sign off, you are clear to build the UI. That is the gate that prevents the blank-RFQ failure mode three weeks later.

Portrait process-flow infographic in teal and green outlining steps to build a specialty equipment quote request tool.
The build a specialty equipment quote request tool with cursor process, step by step.

The Afternoon File Structure: Four Library Files and Three Routes

The project structure for an afternoon build is small on purpose. Here is the tree to paste into your next Cursor prompt:

“` /app /quote page.tsx // the multi-step form UI actions.ts // the server action that validates + submits /api /upload-url route.ts // returns a presigned R2 upload URL /lib qualification.ts // the typed decision tree (already built) schema.ts // Zod schema DERIVED from qualification.ts validators.ts // server-side spec-completeness checks crm.ts // webhook dispatcher to HubSpot/Salesforce/Pipedrive “`

Four files in /lib. One page. One server action. One API route. That is the whole asset.

The follow-up prompt that generates the Zod schema without flattening

This is the second critical prompt. The mistake to avoid is letting Cursor write a parallel Zod schema that looks similar to qualification.ts but drifts from it. Parallel types are how flat schemas creep back in.

“` Now generate /lib/schema.ts.

The Zod schema must be DERIVED from the EquipmentRFQ discriminated union in /lib/qualification.ts. Use z.discriminatedUnion. Do not create parallel field definitions. If qualification.ts changes, schema.ts must break at compile time, not silently diverge.

Export a single schema called EquipmentRFQSchema. “`

Zod’s discriminated union docs confirm the pattern: one schema, one source of truth, compile-time enforcement.

Why server actions, not API routes, for the submit gate

Use a Next.js server action for the form submit, not a traditional /api route. Server actions run on the server regardless of what the browser does with your client JavaScript. That property is what saves you in week two when a buyer’s locked-down browser blocks scripts, applies a strict content security policy, or simply fails to execute your client validation.

The API route is only for the presigned-URL endpoint. Everything else: server action.

How to Handle 8 to 40 MB Engineering PDF Uploads Without Losing Them in the CRM

Specialty equipment buyers send big files. OEM datasheets, drawings, and existing-equipment photos run 8 to 40 MB routinely. Generic form builders reject them. Base64-encoding them into a form payload runs straight into Vercel’s serverless body size limits. And most CRM webhooks strip or mishandle attachments from inbound payloads.

The pattern that works: presigned URL uploads direct to object storage.

Operator Note: The browser uploads the PDF directly to Cloudflare R2 or AWS S3 using a temporary signed URL. Your Next.js server never touches the file bytes. The form only submits the final URL string, which fits in any webhook payload.

The presigned upload prompt for Cursor

“` Create /app/api/upload-url/route.ts.

This route accepts a POST with { filename, contentType, sizeBytes }. Validate that sizeBytes <= 52428800 (50MB ceiling, set 10MB above the 40MB top of the typical 8-40MB range to absorb the occasional outlier datasheet without rejecting it) and that contentType is one of: application/pdf, image/png, image/jpeg, image/heic.

Use Cloudflare R2 (S3-compatible). Generate a presigned PUT URL with a 10-minute expiry. Return { uploadUrl, fileUrl } where fileUrl is the permanent public read URL.

Use environment variables for credentials. Do not hardcode anything. “`

Cloudflare R2’s presigned URL docs and the AWS S3 equivalent cover the credential setup. R2 has no egress fees, which matters when sales is downloading 30 MB datasheets all day.

Why the file URL goes in the CRM note field, not the attachment slot

HubSpot, Salesforce, and Pipedrive all accept inbound webhooks. Their attachment handling is inconsistent. HubSpot’s Forms API is fine with text fields but treats binary attachments through webhooks as a separate problem. Salesforce inbound flows often require an Apex handler for files. Pipedrive’s webhook attachment support is limited compared to its core data fields.

The pattern that round-trips on all three: post the R2 file URL as plain text into a custom field or a note on the contact or deal record. Sales clicks the link, the PDF opens, done. No attachment handling, no MIME drama, no silent failures. If you want belt and suspenders, also email the URL to the assigned AE.

The Server-Side Validation Gate That Stops Blank RFQs From Reaching Sales

Here is the failure mode nobody talks about. Your QA submissions look perfect because you test in Chrome on your MacBook with no extensions. Real buyers do not test that way. They submit from locked-down corporate browsers, sometimes through a VPN, sometimes with strict content security policies or aggressive script-blocking extensions in place.

Client-side JavaScript validation is a UX aid, not a security control. Web platform documentation covers the standard ways it can be disabled (a novalidate attribute on the form, scripts that fail to load, a CSP that blocks inline handlers). When any of that happens, the form can still submit. Your CRM gets garbage.

The fix is to put every spec-completeness check inside a Next.js server action. The button can fire from the browser. The qualified=true flag only gets set after the server has re-validated the payload against EquipmentRFQSchema.

The Cursor prompt for the validator

“` Create /lib/validators.ts and /app/quote/actions.ts.

validators.ts: export validateRFQ(payload) that runs EquipmentRFQSchema.safeParse(payload) and returns either { ok: true, data, qualified: true } or { ok: false, errors }.

actions.ts: export an async server action submitRFQ(formData). It must:

  1. Parse formData into a plain object.
  2. Call validateRFQ.
  3. If invalid, return errors to the client. Do NOT post to the CRM.
  4. If valid, call /lib/crm.ts to dispatch the webhook with

qualified: true.

  1. Log every submission attempt (valid or invalid) to console with

a timestamp and the equipmentType.

Do not use ‘use client’ anywhere in actions.ts. “`

The logging step matters. When sales reports blanks in week two, your first move is to grep the Vercel logs for invalid submissions. If you see a stream of failed validations from real prospect domains, the form is doing its job and the failure is upstream (a bad email field, a buyer using a tablet). If you see successful submissions with empty required fields, your validator is wrong and you need to tighten the schema.

The 20-minute diagnostic when sales reports blanks

  1. Open Vercel logs for the last 72 hours. Filter for the submitRFQ action.
  2. Count valid vs. invalid submissions. The ratio is your real qualification rate.
  3. Pull three blank RFQs from the CRM. Match each to a log entry by timestamp.
  4. If the log shows valid=true with empty required specs, your schema has an optional field that should be required. Tighten qualification.ts, redeploy.
  5. If the log shows no entry, the user never hit submit successfully. Check for a network or JavaScript error in the browser. Usually a stricter content security policy on the buyer’s side.

How to Test the Build the Way Procurement Actually Uses It

Testing on your laptop in Chrome is not testing. You are the developer. Your environment is wrong on purpose.

The four-browser pre-launch matrix:

Environment What it catches
Chrome incognito with uBlock Origin enabled Tracking pixels and analytics that block validation
Mobile Safari on iOS Touch handlers and file picker compatibility
Edge with JavaScript partially disabled (DevTools, Settings) The restricted-browser failure mode
Any browser through a corporate VPN proxy TLS interception and script stripping

If all four submit cleanly and the CRM record has every required spec field populated, you are ready for UAT.

The two-AE UAT script

Pick the two AEs who sell the most volume in different equipment categories. Give each one this instruction: “Submit a fake RFQ for the equipment you sell most. Pretend you are a real buyer. Upload a real datasheet from a recent deal. Tell me when you are done.”

Then open the CRM. The record should exist. Every spec field they answered should be populated. The file URL should be clickable and open the PDF. The qualified flag should be true.

If any of those four checks fail, you have a bug. Fix it before launch.

The three metrics that tell you the build is working

  • Spec-completeness rate: RFQs with all required spec fields populated divided by total RFQs submitted. Should be above 95% in week one. Below that means your validator is too loose.
  • RFQ qualification rate: sales-accepted RFQs divided by total RFQs submitted. Should match or beat your previous inbound rate from Typeform or HubSpot forms.
  • Cost per qualified RFQ: paid-media spend plus amortized build cost, divided by sales-accepted RFQs. This is the number your CFO cares about.

For more on closing the loop from form-fill to revenue, see our piece on revenue-based attribution and the principles in A/B testing for lead generation.

When to Stop and Hire an Engineer Instead

The afternoon build works for a specific shape of problem: equipment with two to six discrete categories, qualification trees that branch one or two levels deep, and a CRM that accepts inbound webhooks. If your situation is outside those lines, stop.

Three cases where you should not DIY this:

  1. ITAR or EAR-controlled equipment. Export-controlled specs cannot live in a public form behind Vercel. You need an audited stack, signed data processing agreements, and access controls that an afternoon build cannot provide.
  2. Audit-required industries. Medical device, aerospace, defense, and certain regulated industrial categories require submission logs that survive a compliance audit. Vercel console logs do not count.
  3. Branching deeper than three levels. If qualifying a piece of equipment requires fifteen branching questions and tacit knowledge from a 30-year sales veteran, the qualification tree is not the right artifact. Your sales team needs a configure-price-quote (CPQ) platform, and your marketing site needs a simpler tool that just routes the lead to a human.

If you are in any of these three cases, or if you ship the afternoon build and the spec-completeness rate is sitting below 80% after two weeks of fixes, that is the signal to bring in help.

For a comparison of when a different AI coding assistant fits the job better, our Cursor vs Claude Code for marketers piece covers the decision. For the broader stack of web development services we ship at Elevarus, see full stack development services.

Frequently Asked Questions

Can a marketing manager actually ship a working RFQ tool in an afternoon with Cursor?

Yes, if you follow the qualification-tree-first prompt order and stick to a small file structure. A non-engineer can build a working specialty equipment RFQ tool in four to six hours using Cursor, Next.js, Vercel, and Cloudflare R2. The afternoon assumption breaks if you let Cursor scaffold the UI before the qualification tree, because you then spend the next two weeks debugging a flat schema.

Why does the qualification tree need to come before the UI?

Cursor defaults to UI-first scaffolding because its training data rewards pretty forms. Once a form exists, the data schema gets shaped to fit it. The result is a flat schema dressed up as a multi-step form, which is the exact problem Typeform and HubSpot forms already have. Writing the qualification tree as a typed discriminated union first locks the data shape to how sales actually qualifies, before any UI can flatten it.

How do I handle 8 to 40 MB engineering PDF uploads without breaking the form?

Use presigned URL uploads to Cloudflare R2 or AWS S3, with a 50 MB ceiling, and post only the resulting file URL into a CRM note field. The browser uploads directly to object storage using a temporary signed URL, so your Next.js server never touches the file bytes and you do not hit serverless body size limits. CRM webhook attachment handling is unreliable across HubSpot, Salesforce, and Pipedrive, so the URL-in-note pattern is the only one that round-trips on all three.

Why do RFQs from real prospects arrive blank when QA submissions look fine?

Client-side JavaScript validation is a UX aid, not a security control. It can be disabled with novalidate, bypassed by content security policies, or simply never run when scripts fail to load in a restricted corporate browser. The fix is to put every spec-completeness check inside a Next.js server action that re-validates the payload against your Zod schema before setting qualified=true. The browser can submit anything. The server is the only gate that decides whether the RFQ counts as qualified.

Do I need a database, or can the CRM be the system of record?

For an afternoon build, the CRM is the system of record and you do not need a database. The form submits via a server action, the validator gates the payload, the webhook dispatcher posts the structured data and file URL into HubSpot, Salesforce, or Pipedrive, and the conversation lives there. Add a database only when you outgrow the CRM’s custom field limits or need to version submissions independently.

When should I stop and hire an engineer instead?

Stop if your equipment is ITAR or EAR-controlled, if you are in an audit-required industry like medical devices or defense, or if your qualification tree branches more than three levels deep on tacit sales knowledge. Those situations need access controls, audit logs, and CPQ platforms that an afternoon Cursor build cannot deliver. If your spec-completeness rate stays below 80% after two weeks of fixes, that is also the signal to bring in help.

If you have already shipped a quote tool and sales is telling you the RFQs are arriving blank, or you want a second set of eyes on a build before you launch it, book a free consultation with Elevarus. We audit web assets the same way we build them: tree first, server-validate, URL-not-attachment. We can tell you in 30 minutes whether your current tool is salvageable or whether the afternoon build is the right move.



Ready to put this into action?

Picture of SHANE MCINTYRE

SHANE MCINTYRE

Founder & Executive with a Background in Marketing and Technology | Director of Growth Marketing.