Acceptance, decline, and counter-offers

When you send a quote, the client gets an email with a link to the hosted quote page at /quotes/<token>. From there they have three actions: accept, decline, or request changes.

What the client sees

The hosted quote page shows:

  • Your org's logo and accent color (their portal looks like yours).
  • The full quote — title, line items, totals, deposit info, expiration date.
  • Three buttons: Accept Quote, Decline, Request Changes.
  • A "Download PDF" link for their records.

No account is needed to view or act on a quote — the signing token in the URL grants temporary access.

Acceptance flow

When the client clicks Accept:

  1. The contract status moves to signed with signedAt set to now and signedByName set to the name the client typed. Your saved signature was already on the quote from the moment you sent it, so this single action finalizes the document for both sides.
  2. Based on the quote's billing mode, the appropriate Stripe invoice fires (deposit, full, or draft).
  3. A Project is auto-created with budget = quote total and tied to the contract.
  4. The client lands on /quotes/<token>/accepted with numbered next steps: pay the deposit via the hosted Stripe link, then click "Create portal account" to set up portal access (separate step, prevents bouncing between payment and account creation).

You get an email at your support address (or login email if support_email is unset) letting you know the quote was accepted, plus an in-portal notification.

Project auto-creation

By default, accepting a quote spawns a Project automatically so the work has a home for time tracking, notes, and document sharing. Turn this off per-org in Pricing → Rules via the "Auto-create project on accept" toggle.

The project's name matches the quote's title. If you don't want a project (e.g., for one-off product sales), turn the setting off.

Decline flow

When the client clicks Decline:

  1. The contract status moves to declined. They can optionally leave a reason.
  2. You get a notification (in-portal + email) with their reason if provided.
  3. The hosted quote page replaces the accept/decline buttons with a "This quote was declined" message.

Declined quotes stay in your Contracts list under the Declined filter. They're not deleted — you can re-send a new quote inspired by the declined one in one click.

Counter-offer / request changes

When the client clicks Request Changes:

  1. They get a free-form text box to describe what they want changed (price, scope, timing).
  2. The contract status moves to changes_requested.
  3. You get a notification with their message.
  4. The hosted quote page is locked — they can't accept until you respond.

How you respond

Open the contract from /admin/contracts and you'll see the request inline. You have two options:

  • Send a revised quote — opens the composer pre-filled with the original quote's data. Edit, save, and the client gets a new email with the updated PDF + accept link.
  • Mark as resolved — if you've handled the conversation out-of-band (email, phone), you can resolve the request without sending a new version. The contract goes back to viewed status and the client can accept the original.

Counter-offer caps

To prevent infinite back-and-forth, there's a per-org cap on how many counter-offer rounds a client can request. Default is 3. Configure in Pricing → Rules via quote_max_counter_offers.

After the cap, the "Request Changes" button on the client's hosted page is disabled — they can only accept or decline.

Expiration

If you set an expiration date on the quote and the client doesn't act before it, the contract status moves to expired automatically (via the cron sweep that runs hourly).

After expiration, the client can still view the quote but can't accept. Send them a new quote or use the acceptance grace period setting (in Pricing → Rules) to give them N extra days past expiry. Default is 0 — strict expiry.

Voiding a quote

If you need to pull back a quote before the client acts, open the contract page and click Void. The status moves to voided, the hosted page shows "This quote was voided," and the client can't accept anymore.

Voided quotes are kept in your contracts list for record-keeping. They don't fire any invoices and don't create projects.

What the client sees after accepting

After paying the deposit (if any), the client clicks "Continue to your portal" and lands at /dashboard. Their first portal experience shows:

  • The project that was auto-created from the quote
  • The signed contract with the option to download it
  • An invoice receipt for the deposit (if applicable)
  • A pending balance invoice (if your billing mode is deposit+draft, they'll see this once you finalize and send it later)