Skip to main content

Documentation

No results found.
Features

Proposals & E-Signatures

Proposals is the front half of the invoicing funnel: build a quote with rich content and line items, email the client a tokenized review-and-sign link, and let them accept and e-sign online — typed legal name plus an optional drawn signatur...

Proposals is the front half of the invoicing funnel: build a quote with rich content and line items, email the client a tokenized review-and-sign link, and let them accept and e-sign online — typed legal name plus an optional drawn signature, with a full IP/timestamp audit trail — or decline with a reason. An accepted proposal converts automatically into a draft invoice in Client Billing, ready to send.


The problem

Winning the work happens before the invoice — but proposal tools are their own SaaS subscription (with per-seat pricing and their own client list), and emailing a PDF quote means no signature, no audit trail, no notification when the client opens it, and re-typing everything into the invoicing tool once they say yes.

The fix

A self-contained feature module under app/Features/Proposals/ that reuses the Client Billing client list, adds a proposal builder (rich intro/scope/terms + line items), serves a tokenized accept-and-sign page from the install's own domain, and hands the win straight to Client Billing as a draft invoice — same client, same lines, fresh invoice number.

Like every addon, it's structured as a feature module gated by feature:proposals middleware. The service provider boots unconditionally; routes 404 and the sidebar entry hides when the feature is off. Toggling it on runs the module's migrations. Disabling preserves all data.

What the dashboard gets

Two pages under Dashboard → Proposals (Proposals needs Manager and up; Settings is Admin-only):

  • Proposals — the pipeline: All / Drafts / Sent / Accepted / Declined tabs (the Sent tab shows an awaiting-signature count) plus search by number, title, or client. Each row shows number, client, title, total, a status badge — Draft, Sent, Viewed (the client has opened it), Accepted, Declined, Expired (derived live from the valid-until date, no sweeper job) — valid-until, and created date. Row actions: edit, open/copy the public link, Download PDF, Send (or Resend), Duplicate (fresh draft with a new number and expiry), Convert to invoice (accepted, not yet converted), Start a project (accepted, when Project Tracking is on), and Delete draft (only drafts are deletable — sent proposals are records).
  • Proposal editor — pick a client from the shared invoicing client list (or quick-add one inline, mirrored into the CRM per the invoicing sync setting), set a title, valid-until date, currency, and tax rate, then build line items: description, quantity (decimals allowed), and unit price with live-updating totals; a negative unit price works as a discount line. Three rich-text sections (Tiptap — headings, lists, links, bold/italic/underline) shape the document: Introduction (the pitch), Scope of work (deliverables), and Terms (what the signature agrees to). Once the client has accepted or declined, the proposal locks read-only — duplicate it to start a new version.
  • Settings (admin only) — number prefix (PRO-), default valid-for days, default terms, the auto-convert toggle, PDF email attachments, and the email sender / owner-notification overrides (all falling back to the Client Billing settings, which fall back to Business settings).

The dashboard also gets a Proposals card: proposals awaiting signature (sent, not expired) plus the accepted count over the last 30 days.

What the client sees

The emailed proposal contains the line-item table, totals, an optional PDF attachment, and a Review & sign button pointing at the tokenized public page (/proposal/{token} — a 40-character random token, no login needed):

  • A printable document: business identity block (shared with invoicing), number and valid-until, title, the rich intro/scope sections, the investment table with totals, and terms — plus Download PDF and Print buttons.
  • Accept & sign: typing the full legal name (previewed in a script-style face) plus a consent checkbox is the legal signature; a drawing pad (pointer/touch canvas) is optional. Accepting stamps accepted_at, signed_at, the signature name/email, the visitor's IP address and user agent, and the drawn polylines — atomically, exactly once (a race between two clicks, or an accept racing a decline, lands only the first).
  • Decline with an optional reason — same atomic claim, so a proposal can never be both accepted and declined.
  • Clear state banners afterwards: a green accepted-and-signed block (typed name, drawn signature, timestamp), a declined notice, or an amber expired notice once the valid-until date passes (expired proposals can no longer be accepted).

The first client open records first_viewed_at (shown as Viewed in the dashboard) — managers previewing don't count, and drafts are invisible to the public entirely (managers can preview them while logged in, including the PDF).

A signed-in client doesn't need the emailed link. /members/agreements (the Agreements pill in the account nav, plus a card on the account dashboard) lists every non-draft proposal and contract for clients whose email matches the account, each linking to the tokenized page above. One page for both because both fold into client_invoicing and both hang off the same Client row — a client thinks of them as one pile of paperwork. Reviewing, accepting and signing all stay on the tokenized page. See memberships.md → The account area.

E-signature & audit trail

The signature record stored on the proposal: typed legal name, signer email, IP address, user agent, drawn-signature polylines (rendered as an inline SVG on the page and in the PDF), and the signing timestamp. The PDF's signature block prints "Signed by {name} ({email}) on {date} — IP {ip}" under the script-styled name and drawing — so the downloadable document carries the evidence with it. The typed name + consent checkbox is the signature; drawing is optional.

Conversion to invoice

When auto-convert is on (the default), acceptance immediately creates a draft invoice in Client Billing for the same client: same currency, tax rate, and line items, terms carried over as plain text, fresh INV- number and issue/due dates from the invoicing defaults. Converting is idempotent — converting again returns the same invoice — and the proposal links to what it became. With auto-convert off, the row menu's Convert to invoice does the same thing manually.

Start a project is the pipeline's third leg (Project Tracking must be on). It opens a tracked job carrying the client, the proposal title, and the scope as its description — the tracking page opens with what was actually agreed rather than an empty box. Deliberately manual, like Convert to invoice: the operator decides when paperwork becomes live work. It's idempotent through the project's source morph, and the menu item disappears once a project exists.

Emails

All transactional mail goes through the shared Marketing transport (CampaignMailer), with an optional proposals-specific from name/address (falling back to the invoicing sender): the proposal email to the client (claimed once per proposal so a double-click can't double-send; resending is an explicit dashboard action), an owner notification on acceptance (with the signature audit line), and an owner notification on decline (with the reason). If the transport isn't configured, sending fails gracefully — the copy-link action, PDF download, and public page work without email entirely.

Integrations

  • Client Billing (required) — shares the invoicing_clients list, converts into client_invoices, and reuses the business identity block and currency settings.
  • CRM — sent/accepted/declined events log to the matched contact's interaction timeline (type proposal, matched by client link or email); quick-added clients mirror into the CRM via the invoicing sync setting.
  • Activity Log — sent, accepted-and-signed, declined, and converted events log under the proposals category.

Statuses

Stored: draft → sent → accepted | declined. Displayed: Viewed and Expired are derived live from first_viewed_at and the valid-until date — no sweeper job when an expiry passes. An expired proposal can't be accepted or declined; clear the date or extend it and resend to revive it.

Limitations

  • One signer per proposal — no multi-party or counter-signature flow.
  • The drawn signature is supporting evidence only; the typed name + consent is the operative signature (standard click-wrap e-sign, not a qualified/eIDAS-advanced signature).
  • One currency per proposal, set at creation.
  • Declining is final for that proposal — duplicate it to negotiate a new version.