Creating your first quote
Quotes are the entry point for most client relationships in The Contractor Codex. You build a quote, the client accepts it online, and a Stripe invoice goes out automatically. This page walks through the composer end-to-end.
Where the composer lives
Open /admin/quotes/new from the Quotes tab in the admin nav, or click "Send Quote" from any client's workspace.
The four required fields
- Client — pick from your existing clients, or just type an email and we'll create the record on send. For brand-new prospects you don't need to onboard them as a client first; the portal auto-provisions a Clerk account the moment they accept.
- Title — what shows up at the top of the quote PDF and in every email subject line. Keep it short and project-shaped: "Kitchen remodel", "Q1 SEO retainer", "Logo design".
- Line items — the scope and pricing. See Line items and the library for the full picker.
- Billing mode — how the invoice fires when the client accepts. See Billing modes for the four options.
Everything else (notes, expiration, deposit %, optional add-ons) has defaults set in Pricing → Rules that you can override per quote.
Step-by-step
1. Pick the client
Type a name or email. If a matching client exists, click them; if not, type a fresh email and hit Tab — the composer will hold that email as a "new prospect" and create the client record on send.
For a brand-new prospect, you only need an email. Name, phone, and company can come later — they'll fill in when the client accepts the quote and signs up.
2. Title and notes
The title appears on the PDF and in the email subject. The notes field is optional free-form text — describe the scope in your own words. Markdown is supported (headings, lists, bold).
If your title is the same as a project name, the portal automatically links the resulting project (after acceptance) back to this quote.
3. Line items
The composer ships with a 240-item library across 24 industries. Click "Add from library" to browse, or type to filter. Each library item has a default name, description, unit price, and quantity. Drop it in and edit per quote.
You can also:
- Create custom line items inline — name, description, qty, unit price.
- Mark items optional — they appear on the quote with an opt-in checkbox the client can tick at accept time.
- Mark items recurring — monthly or annual; these don't count toward the quote total or deposit base but show up as ongoing commitments on the PDF.
- Apply the materials-markup rule — a one-click button on each row that auto-marks up by the org default (15%, configurable per org).
4. Billing mode
Pick how invoicing happens when the client accepts:
- Deposit only — collect a deposit %, balance invoiced later.
- Bill in full now — single invoice for the full amount.
- Draft for completion — no upfront invoice; create a draft you'll finalize when work is done.
- Deposit + draft — collect deposit upfront, draft balance for completion.
The full breakdown is in Billing modes.
5. (Optional) Deposit %, expiration, discount
- Deposit % — used by Deposit-only and Deposit-and-draft modes. Defaults to 25% (configurable in Pricing → Rules).
- Expires at — if set, the quote becomes uneditable to the client after this date. Default is no expiration.
- Discount — flat dollar or percentage applied to the required subtotal (not optional rows).
6. Sign as company
The composer has a sticky Sign as company card directly under the totals box on the right side. Your saved signature is pre-loaded; pick a different mode (typed vs drawn) per send if you want to. The Send button stays disabled until you've captured a signature.
This is what gets applied to the quote PDF at the moment you click Send, so the recipient opens a fully-authorized document and their acceptance is the only signature left to add. No separate countersign step.
If you haven't saved a signature yet, set one once at Settings → Signatures and every future quote uses it by default.
7. Preview and send
When you're ready, click Send Quote. The client receives an email with:
- The PDF attached, your signature already on it
- A link to the hosted quote at
/quotes/<token> - A clear "Accept" CTA on the hosted page
What the client sees
The client opens the email, clicks the link, and lands on a branded page with the quote's full line-item table, totals, deposit info, and accept/decline buttons. They don't need an account to view or accept — accepting auto-provisions one.
What you see after sending
The quote moves to status: sent on /admin/contracts (quotes are stored as Contract records of kind "quote"). You'll get an in-portal notification when:
- The client views the quote
- They accept, decline, or counter-offer
- A deposit invoice is paid (if applicable)
Saved quote templates
If you send similar quotes often, save the current composer state as a template from the "Save as template" button. Templates land on /admin/contracts/templates and can be loaded into a new quote in one click.
Existing client vs new prospect
The composer handles both seamlessly:
Existing client (you've worked with them before, they have a Customer record):
- Pick from the dropdown — start typing their name or email and they'll appear.
- The quote inherits their saved address, company info, and subscription type (if any).
- After acceptance, the new project is filed under their existing workspace.
New prospect (they don't have a Client record yet):
- Type their email directly into the client field and hit Tab.
- The composer marks them as a "new prospect" — no Client record is created until you actually send the quote.
- After acceptance: a Client record is created automatically with the prospect's email; if they typed a name on the accept page, it's filled in; phone and company can be added later.
You can mix these on the same quote — e.g., send a quote to a brand-new prospect that references one of your existing clients as a referrer in the notes field. The portal doesn't care about the relationship; it just bills the prospect.
Quote expiration in practice
The Expires at field defaults to empty (no expiration). Pick a date if:
- The pricing is time-sensitive (a discount that ends, materials cost that varies).
- You want to prevent stale acceptances on something you don't want to do anymore.
- You're sending a competitive quote and want to force a decision.
What happens after expiration:
- The hosted quote page shows "This quote has expired" instead of the accept buttons.
- The contract status moves from
sentorviewedtoexpiredvia the hourly cron sweep. - The client can still view the quote (PDF download still works) — they just can't accept.
Acceptance grace period
If you want to be flexible after expiration, set the org-level quote_acceptance_grace_days in Pricing → Rules. Default is 0 (strict expiry). Setting it to, say, 7 means an expired quote can still be accepted within 7 days of its expiration date.
Use this when you don't want to chase clients who missed the deadline by a day or two.
Quote-specific deposit override
Each quote can override the org-default deposit %. The composer's Deposit % field accepts any integer 0–100:
0(or blank) — no deposit, regardless of the billing mode. Useful when you want to use Deposit + draft mode but bill the full amount at completion.1to99— custom %. Applied to the required subtotal.100— collect 100% upfront. Equivalent to Bill-now mode.
The override doesn't change the org default — just this one quote.
Currency and tax
The composer defaults to USD. Each line item has a currency field — set it to EUR, GBP, CAD, etc. for international quotes. All line items on the same quote must use the same currency. The composer enforces this on save.
Tax is handled by Stripe, not the portal. If you have Stripe Tax enabled, the resulting invoice will calculate tax based on the client's billing address. The quote PDF itself doesn't show tax — it shows pre-tax line items + total, with a note that tax may apply at invoice time.
The materials-markup rule explained
Click the materials-markup button on any row to apply your org's default markup (default 15%, configurable in Pricing → Rules via quote_materials_markup_pct).
What it does:
- Multiplies the row's unit price by
(1 + markup%). - Sets a flag on the row marking it as materials-passthrough.
- Adds a small "(includes materials markup)" note on the PDF under the line item.
Useful when you're passing through a vendor cost (lumber, parts, software licenses) and want the markup transparent rather than hidden inside an opaque rate.
You can disable the materials-markup feature for your org entirely by setting quote_materials_markup_pct to 0. The button then disappears from the composer.
Sending without an email
If you want to generate the PDF + share it via a different channel (text the link, hand-deliver the PDF, attach in a separate email), pick the Send without email option in the composer. The portal:
- Generates the PDF + the signing link.
- Skips the email send step.
- Shows you the link to copy + the PDF to download.
The hosted accept page still works — the client just gets the link from you out-of-band.
Edit / void / resend after sending
Once a quote is sent, you can't edit the line items (the client already has the PDF). Three options:
- Resend — re-fires the same email with the original PDF + link. Useful if the client lost the email.
- Void — pulls the quote back; client can't accept anymore. Status moves to
voided. - Send a revised quote — opens the composer pre-filled with the original, lets you edit, sends a new quote (and voids the old one if you tick the option).
Editing a quote that's been accepted is impossible — the contract is signed, the invoice is fired. If terms need to change post-acceptance, send a change order (a separate contract record of kind: change_order) referencing the original.
Quote analytics
Each quote tracks:
- Sent at — when the email went out.
- First viewed at — when the client clicked the link. Status moves from
senttoviewed. - Accepted / declined / counter at — terminal action timestamp.
- Total dollar value — for revenue projections.
Open /admin/contracts/reports to see aggregate metrics: send-to-accept conversion rate, average time to accept, dollar value of open quotes, decline reasons, etc.
What can go wrong — common failure modes
"Client never opened the email" — Check spam folder. If your custom email domain isn't verified, emails ship from the platform domain and are more likely to land in spam.
"Client opened the link but didn't accept" — Most common reason: they want to talk through scope first. The link stays valid; follow up with a call/email and they'll come back. Use Request Changes if you want to invite a counter-offer.
"Quote sent but no Stripe invoice fired on acceptance" — Your Stripe Connect account isn't fully onboarded (charges_enabled = false). Finish Stripe setup, then resend the quote.
"Wrong client got the quote" — Void it immediately, then resend to the right client. The voided quote shows up in your activity log so you have a paper trail.
"Client says PDF is broken" — Usually a corporate email gateway stripping the attachment. Send them the hosted quote link directly — they can download the PDF from there.
