Reference → Contacts — Reference

Contacts — Reference

The Contacts surface is the customer-relationship-record plane for every SGEN site. It owns every person or organization the business holds a relationship with — leads who came in through a form, customers who placed an order, vendors who supplied a service, partners on a referral channel. Anything that needs a durable record with tags, custom fields, and a communication history traces back to a record managed here.

This page is a reference for platform engineers and integrators who need to understand the surface before extending it, scripting against it, or scoping a CRM workflow. Customer-facing how-tos live in the customer docs set; this page describes the shape of the surface, not the steps to drive it.


Overview

Contacts live under the Contacts module in SG-Admin. The module renders four primary views — the contact list, the contact create / edit form, the per-contact timeline (communication history + activity), and the segment-list editor — and exposes a small set of write operations for create, update, tag, segment-membership, communication-log append, and lifecycle transitions.

The module is paired by convention: one half renders the views and prepares the data, the other half handles writes and returns AJAX responses. Engineers reading the SG-Admin source will see this split across two controller files; the reference below describes the combined surface as it appears to a calling integration.

A contact record is a superset, not a synonym, of a lead. The Leads surface (adjacent) is the capture and qualification layer — form submissions, lead scoring, status workflow, assignment to staff. When a lead qualifies or converts, it becomes a contact (or attaches to an existing contact). A contact can exist without ever being a lead (manual entry, imported list, vendor record); a lead always graduates into a contact when it converts.

Where it lives in SG-Admin:

  • Sidebar: SG-Admin → Contacts
  • URL prefix: /sg-admin/contacts/
  • View templates: application/views/Admin/Contacts/
The module surface is intentionally broad — a contact carries identity, tagging, custom fields, segment memberships, and a full communication history. Heavier downstream concerns (campaign send-and-track, lead-scoring rules, opportunity pipeline) live in adjacent surfaces. This page covers the contact-record plane and its immediate dependencies.
┌────────────────────────────────────────────────────────────────────────────┐│ SG-Admin → Contacts [+ Add contact] │├────────────────────────────────────────────────────────────────────────────┤│ Name Email Tags Last touch ││ ────────────────── ────────────────────── ────────────── ──────────── ││ Northgate Solutions ops@northgatesolutions.com customer, ent Email · 1d ││ Sarah Kim sarah@kim.co lead, q2-campaign Form · 3h ││ Tomas Ribeiro tomas@cidaderio.com customer, smb Call · 2d ││ Northwind Vendor billing@northwind.io vendor Invoice · 5d ││ Priya Nair priya@nair.dev partner, referrer Email · 1w ││ ││ [⋯ Edit] [⋯ Add tag] [⋯ Add to segment] [⋯ Log communication] │└────────────────────────────────────────────────────────────────────────────┘

Actions

The Contacts surface exposes the following operations. Each is described by what it does to the data, not by its internal method name.

List and filter

Returns the contact records visible to the current operator, paginated, with name, email, tags, segments, and last-touch columns. Supports column sort, free-text filter across name and email, tag filter (any of, all of, none of), segment filter, custom-field filter (when configured), and per-page count. The last-touch column resolves from the communication history — contacts with no logged communication show their created_at instead.

Create contact

Opens an empty contact form. Required fields at minimum: display name (person name or organization name). Optional fields cover email, phone, organization, role, source, address, locale, time zone, and any custom fields configured for the site. On submit, the surface validates uniqueness against the configured de-duplication rule (default: email match) and either persists a new record or surfaces a merge prompt if a match is found.

Edit contact

Loads an existing record into the same form shape used for create, pre-populated. Submit replaces the stored profile with the posted values; fields left blank do not clear server-side values (the form is partial-update by default). Custom-field values are included in the same submit.

Tag

Adds or removes a tag from a contact. Tags are a flat free-text taxonomy — any string is a valid tag, but the surface surfaces auto-complete from the existing tag set to reduce drift. Bulk-tag is supported across a checked set of contacts in a single request.

Add to segment

Attaches a contact to a named segment. Segments are saved filter expressions plus an explicit-membership override — a contact can be in a segment because it matches the segment's filter (dynamic membership) or because it was explicitly added (static membership). Explicit additions persist even when the filter would no longer match.

Edit custom fields

Custom fields are a per-site configuration — Settings → Custom Fields defines the field set, types (text / number / date / select / multi-select / boolean), and per-field validation. Editing a contact's custom-field values uses the standard edit form; configuring the field set itself is a separate write path under Settings.

Log communication

Appends an entry to the contact's communication history. Each entry carries a channel (email / call / meeting / form / chat / note), a direction (inbound / outbound), a timestamp, an attributed staff member (optional), a summary, and an optional reference to the originating record (the email ID, the call recording URL, the form submission ID). Automated channels (email sent from a campaign, form submission from the site) write entries automatically; manual channels (call, meeting, note) are operator-entered.

Merge contacts

Combines two contact records into one. The operator selects the surviving record and the duplicate. The duplicate's tags, segment memberships, custom-field values (non-conflicting), and communication history are transferred to the survivor. Conflicting field values surface a per-field choice. The duplicate record is then archived (not deleted) for audit.

Bulk import

Accepts a CSV or paste-block and applies it as contact records in a single transaction. Each row is validated independently; the surface returns a per-row pass / fail report and applies only the passing rows. De-duplication runs per row; duplicates surface a merge / skip / overwrite choice per row.

Soft delete

Marks the record archived. Archived contacts disappear from the default list view but remain in the database, queryable via the archived filter. Communication history, tag history, and segment-membership history are retained.

Restore

Reverses a soft delete. The record returns to the active list with its prior data intact.


Data model

A contact record carries the following fields. Field names below are the conceptual shape — the on-disk column names match closely but are not contractually stable across releases.

FieldTypeNotes
idintegerPrimary key. Stable across edits.
display_namestringPerson name or organization name. Required.
kindenumperson or organization. Defaults to person.
emailstringPrimary email. Optional but used for default de-duplication.
phonestringPrimary phone. Optional.
organization_idintegerWhen kind = person, optional FK to a separate organization contact.
rolestringTitle or function at the organization. Optional.
sourcestringHow the contact entered the system (form name, import batch, manual).
tagsarrayFlat tag taxonomy.
segmentsarraySegment IDs (membership cache; ground truth lives in the segment edges table).
custom_fieldsjsonPer-site custom-field values, keyed by field slug.
statusenumactive, archived.
addressjsonOptional. Per-line address fields.
localestringOptional. Used for outbound communication localization.
time_zonestringOptional. Used for scheduled outbound timing.
lead_idintegerOptional. Set when the contact graduated from a Lead record.
created_attimestampSet on create, immutable.
updated_attimestampTouched on any edit.
Tags vs segments. Tags are operator-applied labels with no logic attached. Segments are filter expressions that resolve to a dynamic membership set, with optional explicit-add overrides. A tag is data; a segment is a query. Use tags for stable labels (customer, vendor, enterprise); use segments for behavioral or campaign membership (opened-q2-newsletter, viewed-pricing-3x).

Custom field semantics. Each custom field carries a slug, a type, and per-type validation. Multi-select custom fields persist as arrays; date fields persist as ISO-8601; boolean fields persist as native true / false. Renaming or retiring a custom field does not rewrite stored values — the historical values remain queryable under the original slug.

Communication history semantics. The history is append-only. Edits to a logged entry write a new row referencing the original; the original is retained. Deletions soft-delete the row; the entry remains in the audit-only view.

Lead linkage. The lead_id field is the bridge to the Leads surface. When set, the contact's source and initial tags were inherited from the Lead's capture record. Subsequent edits do not propagate back to the Lead record — the Lead is the capture history, the Contact is the active relationship.

CONTACT RECORD├── id integer primary key├── display_name string person or organization name├── kind enum person | organization├── email, phone primary contact channels├── organization_id integer optional FK (person → org)├── role string title at organization├── source string capture origin├── tags array flat free-text labels├── segments array membership cache (truth in edges)├── custom_fields json per-site config├── status enum active | archived├── address, locale, time_zone outbound context├── lead_id integer optional FK to Leads└── timestamps created_at, updated_at↓ referenced byCOMMUNICATION HISTORY ENTRY(channel, direction, timestamp,staff_id, summary, source_ref)↓ append-onlyedits write new row,original retained

Permissions

Access to the Contacts surface is gated at two layers.

Layer 1 — admin gate. Every action under SG-Admin passes through the platform's standard admin access check at request entry. An unauthenticated request never reaches the Contacts surface.

Layer 2 — per-action capability. Within SG-Admin, each Contacts action checks a capability associated with the calling operator's role. The default role configuration ships with three roles — Administrator, Editor, Viewer — and the capability map is:

CapabilityAdministratorEditorViewer
List contacts
Create contact
Edit contact
Tag (add or remove)
Add to segment (explicit)
Edit custom-field values
Configure custom-field set
Log communication
Merge contacts
Bulk import
Soft delete
Restore
Custom roles defined under Settings → Roles override the default map. The capability slugs are stable; the role slugs are configurable.

Ownership rule. A contact can carry an assigned owner (staff member responsible for the relationship). Operators with the edit-own-contacts-only capability scope see and edit only contacts where the owner matches them. The default roles above hold the broader scope; custom roles can narrow it.

PII gate. Contact records carry PII by definition. Bulk exports and bulk imports of contacts emit an Activity Log entry tagged contacts.pii_movement for downstream review. Single-record reads and writes do not raise the tag; only bulk movement does.

Audit. Every write — create, edit, tag, segment, custom-field edit, communication log, merge, import, soft delete, restore — emits an Activity Log entry. The log records the acting operator, the target contact, the change shape (field-level diff for edits), and the bulk-tag tag when applicable. Communication-log entries are themselves the audit trail for the communication history surface.

REQUEST│▼┌───────────────────────────┐│ Admin gate │ unauth → reject│ (every /sg-admin/* call) │└─────────────┬─────────────┘│ authed▼┌───────────────────────────┐│ Capability check │ role lacks cap → reject│ (per-action) │└─────────────┬─────────────┘│ allowed▼┌───────────────────────────┐│ Ownership scope │ edit-own-only role →│ │ filter to assigned owner└─────────────┬─────────────┘│ in scope▼┌───────────────────────────┐│ PII gate │ bulk import / export →│ │ contacts.pii_movement tag└─────────────┬─────────────┘│ tagged▼action executes│▼Activity Log entry

Related references

  • Leads — Reference. Capture-and-qualify layer. Leads graduate into contacts via the lead_id link. The Lead record retains the capture history; the Contact carries the active relationship.
  • Segments — Reference. Saved filter expressions plus explicit-membership overrides. Segment membership on a contact is a cache; truth lives in the segment edges table.
  • Custom Fields — Reference. Defines the per-site custom-field set, types, and validation. Contact custom-field values are keyed by the field slug.
  • Settings — Reference. Owns the de-duplication rule, the PII pattern catalog, the bulk-import schema, and Activity Log retention.
  • Staff — Reference. Contact owners attribute to staff records. Re-attribution applies on staff soft-delete.
  • Communication History — Reference. The append-only ledger of contact touches. Channel adapters (email send, form receive, call log) write entries automatically; manual entries are operator-authored.
  • Activity Log — Reference. Source of the contacts audit trail. Every write emits an entry; the contacts.pii_movement tag is the bridge for downstream review.
  • Exports — Reference. The contacts subject draws from this surface's data model. Bulk exports of contacts raise the PII tag.
For the corresponding customer-facing walkthrough — adding a contact, tagging a segment for an email campaign, merging two records — see the Contacts section of the customer docs at /docs/contacts.
On this page