User Guide

Contracts & signing

Author and send agreements (offer letters, NDAs, MSAs, contractor agreements) for in-app or magic-link signature.

State machine

Draft→Sent→Signed→Countersigned

Side states (off the happy path):

WithdrawnRevokedExpiredVoid
Contracts list — every firm-authored agreement with status pill.  Filter by kind, status, recipient.  Click to drill into the signature audit rail.
Contracts list — every firm-authored agreement with status pill. Filter by kind, status, recipient. Click to drill into the signature audit rail.

Two delivery modes

🔐

In-app signing

Recipient has a TTP login. Signs from /contracts/mine after a notification email with a deep link.
🔗

Magic-link signing

Recipient has no account (new hire, pre-vetting 1099). Email carries a token-bearing link (HMAC-signed, 14-day TTL).

Same audit trail either way: typed name, IP (redacted prefix), user-agent, body hash SHA-256 of the body at sign time so auditors can prove what was signed.

Body hash binding
Every signature row carries a body_hash matching the contract's body at sign time. The hash is on the PDF's appended audit page — viewers can verify the signature was made against the exact text they're looking at, not a later-edited version.

Template library

📚

Built-in

Ship with TTP, read-only. 6 generic HTML templates (Offer Letter, NDA, MSA, Contractor Agreement, NCA, plus 3 PDF samples). Per-tenant opt-out for templates you don't want surfaced.
✏️

Tenant-owned

Your custom HTML or uploaded PDF. Admin CRUD under Settings → Contract Templates. Token substitution at compose time.

Templates use {placeholder} tokens that auto-fill at compose time (recipientName, companyName, todayDate, etc.). Custom tokens become form inputs.

Snapshot at compose
Template file bytes / HTML body are copied into a contract-specific path at compose time, so deactivating or editing a template later doesn't affect already-sent contracts. Each sent contract has its own immutable snapshot.
Tip
Auto-reminders fire lazily on list reads — when a sent contract is (expires_at − reminder_days_before_expiry) ≤ today and hasn't already been reminded. Bulk-stamps the per- contract last_reminder_sent_at post-send so a re-fire never spams the recipient.

Send + Resend dialog — Cc + personal note

Both the "Send for Signature" and "Resend" buttons open a shared dialog with three fields: the recipient (locked to the contract's target), a Cc field that pre-populates from the tenant's Contract email Cc setting (with a "Cc me" chip that appends the caller's own email), and an optional 2000-char personal note rendered as an emerald card above the sign CTA. The note text itself is deliberately NOT persisted — it's transient courtesy text that may carry PII. Audit metadata records the Cc list + hasPersonalNote + personalNoteLength. Auto-reminders deliberately skip Cc + notes (no admin in the loop).

Two distinct expiry dates

⏰

expires_at

Recipient sign-by deadline. Past this and unsigned, the contract is auto-expired.
📆

term_ends_at

Agreement term end / renewal date. Drives the compliance expiry reminder pipeline for signed contracts that need renewal (insurance certs, COIs).