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 State | Warning | Behavior 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:
- Signed at current version → Skip (no action, no email)
- Signed at older version → Create new pending record (re-signing required for updated terms)
- Pending → Resend existing token (acts as a reminder, no new record created)
- 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:
- Workspace custom domain — A verified sender identity configured in Settings
- Workspace fallback domain —
{slug}-{shortId}@[FALLBACK_SENDER_DOMAIN] - Platform sender —
notifications@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
| State | What is Displayed |
|---|---|
| Pending | Agreement 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
- The page loads the agreement via
GET /api/agreements/sign?token=[id] - The contact reads the agreement body (rendered from the live template body, not a snapshot)
- The contact checks "I have read and agree to the terms above"
- The contact types their full name
- The "I Agree" button becomes enabled (requires both checkbox and name)
- On click,
POST /api/agreements/signis called with the token andsigned_by_name
What Happens on Sign
The signing API (using createAdminClient() — no authenticated user context) updates the
contact_agreements row:
| Field | Value |
|---|---|
status | 'signed' |
snapshot_body | The template's body_html at time of signing |
template_version | The template's current version number |
signed_by_name | The name typed by the signer |
signed_at | Current timestamp |
ip_address | Extracted 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_bodyHTML (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_byfield isNULL(automated, no human sender) - The same dedup logic applies (skip signed at current version, resend pending, create new for older version)