Agreements
Dispatch & Signing

Dispatch & Signing

This page covers how agreements are sent to contacts, the public signing experience, and how signed records are stored and displayed.

Sending Agreements

Agreements are sent from the Agreements section on the Contact Detail page. The section appears after the Transactions section and includes a "Send Agreement" button.

Send Agreement Dialog

Clicking "Send Agreement" opens a multi-select template picker that lists all active templates. Archived templates are excluded. If no active templates exist, the dialog shows: "No active templates. Create one in Settings → Agreement Templates."

Each template in the picker displays contextual warnings based on existing agreements for that contact:

Existing StateWarningBehavior on Send
No existing agreement(none)Creates new pending agreement
Pending agreement exists"⚠️ Already sent — will resend reminder"Resends existing token (no new record)
Signed at current version"✓ Already signed (current version) — will be skipped"Skipped entirely
Signed at older version"ℹ️ Signed vN — new version vM available, will send for re-signing"Creates new pending agreement for the new version

After selecting templates, click "Send (N)" to dispatch. The system sends one email containing all selected agreements with individual "Review & Sign" buttons for each.

Dedup Logic

The dispatch service applies dedup logic per template:

  1. Signed at current version → Skip (no action, no email)
  2. Signed at older version → Create new pending record (re-signing required for updated terms)
  3. Pending → Resend existing token (acts as a reminder, no new record created)
  4. No existing record → Create new pending agreement

Version-aware re-signing: A signature on v2 covers v2's terms. If the template is updated to v3, the contact has not agreed to v3's terms. The system creates a new pending agreement for v3. This is standard practice in compliance workflows.

Email Template

The agreement email is a transactional email (no unsubscribe link required). The sender address is resolved using a fallback chain:

  1. Workspace custom domain — A verified sender identity configured in Settings
  2. Workspace fallback domain{slug}-{shortId}@[FALLBACK_SENDER_DOMAIN]
  3. Platform sendernotifications@app.gordoncrm.com (last resort)

The email is branded with the workspace name:

  • Subject: "[Workspace Name]: Please sign [Template Title]" (single) or "[Workspace Name]: N agreements to sign" (multiple)
  • Header: "[Workspace Name] — Agreement to Sign"
  • Body: "You have been sent the following agreement from [Workspace Name] to review and sign:"
  • Footer: "Sent by [Workspace Name]. This is an automated message..."

Each agreement is listed with its title and a "Review & Sign" button linking to /agree/[token].

Resending

Pending agreements display a resend (↺) icon on the Contact Detail page. Clicking it re-sends the same agreement email with the same signing token.

Cancelling

Pending agreements display a cancel (✕) icon on the Contact Detail page. Clicking it triggers a double-confirmation prompt, then permanently deletes the pending contact_agreements record. The signing link becomes invalid — visiting /agree/[token] after cancellation shows "Agreement Not Found." Only pending agreements can be cancelled; signed agreements cannot be deleted.


Public Signing Page

Each agreement has a public signing page at:

https://app.gordoncrm.com/agree/[token]

The [token] is the contact_agreements.id (UUID). The page is unauthenticated — the middleware explicitly allows the /agree route without login.

Page States

StateWhat is Displayed
PendingAgreement title, rendered body HTML, consent checkbox, full name input, "I Agree" button
Already Signed"Already Signed" message with the signed date. No option to re-sign.
Just Signed"Signed Successfully" confirmation with green checkmark
Not Found"Agreement Not Found" error page. No sensitive information is leaked.

Signing Flow

  1. The page loads the agreement via GET /api/agreements/sign?token=[id]
  2. The contact reads the agreement body (rendered from the live template body, not a snapshot)
  3. The contact checks "I have read and agree to the terms above"
  4. The contact types their full name
  5. The "I Agree" button becomes enabled (requires both checkbox and name)
  6. On click, POST /api/agreements/sign is called with the token and signed_by_name

What Happens on Sign

The signing API (using createAdminClient() — no authenticated user context) updates the contact_agreements row:

FieldValue
status'signed'
snapshot_bodyThe template's body_html at time of signing
template_versionThe template's current version number
signed_by_nameThe name typed by the signer
signed_atCurrent timestamp
ip_addressExtracted from request headers

Snapshot Integrity

The snapshot_body captures the template body at the moment of signing. If the template is later edited, the snapshot is unaffected — it always reflects the exact text the signer agreed to. This is critical for legal and compliance purposes.


Contact Detail Integration

Agreements Section

The Agreements section on the Contact Detail page displays all agreements sent to the contact, sorted by most recent first. Each row shows:

  • Template title
  • Status badge: Pending (amber) or Signed vN (green)
  • Action buttons:
    • Pending: Cancel (✕) icon (double-confirmation prompt, deletes the pending record) and Resend (↺) icon
    • Signed: View (eye) icon → opens a dialog with the snapshot body and audit metadata

Viewing Signed Agreements

The signed agreement viewer dialog displays:

  • Template title and version in the header
  • The snapshot_body HTML (not the live template — the exact text that was signed)
  • Audit metadata: signed by name, signed at timestamp, IP address

Timeline Integration

Signed agreements appear in the contact's Notes/Timeline section as timeline entries. Each entry displays:

  • "Signed: [Template Title] (vN)"
  • A green FileSignature badge
  • Sorted chronologically among other notes, task notes, and appointment notes

Automation Integration

Agreements can be dispatched automatically via the send_agreement automation action type. See Automations → Actions for full documentation.

When triggered by an automation:

  • The sent_by field is NULL (automated, no human sender)
  • The same dedup logic applies (skip signed at current version, resend pending, create new for older version)