Skip to main content

Documentation

No results found.
Features

Contracts

Contracts closes the gap between winning the work and billing for it: build a contract from a reusable template with merge fields, email the client a tokenized review-and-sign link, and let them e-sign online — typed name in a choice of scr...

Contracts closes the gap between winning the work and billing for it: build a contract from a reusable template with merge fields, email the client a tokenized review-and-sign link, and let them e-sign online — typed name in a choice of script fonts, a drawn signature, or an uploaded signature image, with a full IP/timestamp audit trail — or decline with a reason. Signing automatically flips the client from Prospect to Active in Client Billing, and an optional gate can block sending invoices until a signed contract exists.


The problem

Every new client relationship starts with a contract — but e-sign tools are their own SaaS subscription with per-document pricing, emailing a PDF back and forth means no audit trail and no notification when the client opens it, and nothing connects the signed contract to the invoicing that follows: the client still has to be manually marked as won, and nothing stops an invoice going out before the paperwork is in.

The fix

A self-contained feature module under app/Features/Contracts/ that reuses the Client Billing client list, adds reusable contract templates with merge fields, serves a tokenized sign-online page from the install's own domain, and hands the win straight back to invoicing — the signer's client record flips to Active, and (optionally) invoices stay blocked until a signed contract exists.

Like every addon, it's structured as a feature module gated by feature:contracts 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

Three pages under Dashboard → Contracts (Contracts and Templates need Manager and up; Settings is Admin-only):

  • Contracts — the pipeline: All / Drafts / Sent / Signed / Declined tabs (the Sent tab shows an awaiting-signature count) plus search by number, title, or client. Each row shows number, client, title, a status badge — Draft, Sent, Viewed (the client has opened it), Signed, Declined, Expired (derived live from the valid-until date, no sweeper job) — and the sent/signed dates. Row actions: edit, open/copy the public link, Download PDF, and Delete draft (only drafts are deletable — sent contracts are records).
  • Contract editor — pick a client from the shared invoicing client list, set a title and valid-until date, then either write the body in the rich-text editor (Tiptap — headings, lists, links, bold/italic/underline) or pick a template and hit Apply template: the template body is materialized into the contract with every merge field resolved for the chosen client (which is why a client must be chosen first). Send resolves any remaining merge tokens once more, emails the review-and-sign link, and stamps the sent status; the public signing link with a copy button shows on every sent contract. Once the client has signed or declined, the contract locks read-only with the signature and its audit line displayed.
  • Templates — create and edit reusable contract bodies with the same rich-text editor, plus a hint panel listing every available merge field. Deleting a template never touches contracts already created from it.
  • Settings (admin only) — number prefix (CON-), default valid-for days, the Mark client Active when they sign toggle (on by default), the Require a signed contract before sending invoices gate (off by default), 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 Contracts card: contracts awaiting signature (sent, not expired) plus the signed count over the last 30 days.

Templates & merge fields

Templates are reusable bodies with {{token}} merge fields, resolved when the template is applied (and once more on send, so hand-typed tokens work too). Unknown tokens are left visible rather than silently vanishing. Available fields:

Token Resolves to
{{client_name}} Client contact name
{{client_company}} Client company
{{client_email}} Client email
{{client_address}} Client address
{{business_name}} Your business name (from the invoicing/Business settings)
{{business_email}} Your business email
{{contract_title}} The contract's title (resolved at send)
{{today}} Today's date, localized

What the client sees

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

  • A printable document: business identity block (shared with invoicing), number and sign-by date, title, "Prepared for" client block, and the full contract body — plus Download PDF and Print buttons.
  • Sign: the client enters their full legal name and email (the audit trail), then adds a signature three ways — Type it (previewed live in a choice of four script-style faces), Draw it on a pointer/touch pad, or Upload an image of it — checks the consent box, and clicks Sign contract. Signing stamps signed_at, the signature name/email, the visitor's IP address and user agent, and the signature itself (font choice, drawn polylines, or the stored image) — atomically, exactly once (a race between two clicks, or a sign racing a decline, lands only the first).
  • Decline with an optional reason — same atomic claim, so a contract can never be both signed and declined.
  • Clear state banners afterwards: a green signed block (the signature rendered in its chosen form, plus "Signed by {name} ({email}) on {date}"), a declined notice, or an amber expired notice once the sign-by date passes (expired contracts can no longer be signed).

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 contract and proposal for clients whose email matches the account, each linking to the tokenized page above — signing itself always happens there. See proposals.md and memberships.md → The account area.

Signing methods & audit trail

Three signature methods, all sanitised server-side (the browser payload is never trusted):

  • Typed — the client types their name and picks one of four script-style font faces (OS font stacks with a cursive fallback, so nothing is downloaded and PDFs degrade gracefully). The font choice is whitelisted server-side.
  • Drawn — a pointer/touch canvas; strokes are sanitised into integer polylines (hostile oversized payloads are capped) and rendered back as an inline SVG on the page and in the PDF.
  • Uploaded — a PNG/JPG/WebP/GIF image of the signature (4 MB max), stored on the public disk and embedded in the page and the PDF.

The stored record: signature mode, typed-font choice or drawn polylines or image path, signer name and email, IP address, user agent, and the signing timestamp. The PDF's signature block prints the signature in its chosen form with "Signed by {name} ({email}) on {date} — IP {ip}" beneath it — so the downloadable document carries the evidence with it.

Client activation

When Mark client Active when they sign is on (the default), signing flips the client's Client Billing status from Prospect to Active — the moment the paperwork lands, the client record reflects it. Turn the setting off to manage client status by hand.

A signed contract's row menu also offers Start a project when Project Tracking is on, opening a tracked job for the same client with the contract's title and body as its brief. Manual by design — signing usually precedes any work starting — and idempotent, so the item hides once a project exists. Same ProjectStarter the proposals list uses.

The invoice gate

When Require a signed contract before sending invoices is on (off by default), sending an invoice to a client with no signed contract is blocked with a clear message — send them a contract first. The gate applies to both dashboard send paths (the invoice list's Send action and the editor's Save & send) and never blocks estimates, so the quote → contract → invoice funnel stays intact. It only takes effect while the Contracts feature is enabled.

Emails

All transactional mail goes through the shared Marketing transport (CampaignMailer), with an optional contracts-specific from name/address (falling back to the invoicing sender): the contract email to the client (claimed once per contract so a double-click can't double-send; resending is an explicit dashboard action), an owner notification on signing (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, flips the client's status on signing, gates invoice sending, and reuses the business identity block.
  • CRM — sent/signed/declined events log to the matched contact's interaction timeline (type contract, matched by client link or email).
  • Activity Log — sent, signed, and declined events log under the contracts category.

Statuses

Stored: draft → sent → signed | 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 contract can't be signed or declined; clear the date or extend it and resend to revive it.

Troubleshooting

  • "Apply template" does nothing — a client must be chosen first; merge fields resolve against the client.
  • The Send button is missing — save the draft first, and make sure the client has an email address. The public link with a copy button works without email at all.
  • A merge token shows literally on the contract — it isn't one of the supported tokens (see the table above); unknown tokens are deliberately left visible. Fix the spelling in the template and re-apply.
  • The client says they can't sign — check the contract hasn't expired (extend the valid-until date and resend) and hasn't already been declined. Declining is final for that contract; create a new one to try again.
  • An invoice won't send — if the gate setting is on, the client needs at least one signed contract first. Estimates always send.

Limitations

  • One signer per contract — no multi-party or counter-signature flow.
  • Standard click-wrap e-sign (typed/drawn/uploaded signature + consent + audit trail), not a qualified/eIDAS-advanced signature.
  • Declining is final for that contract — create a new one to negotiate a new version.