Contacts
A contact holds the customer context your team uses to decide who to reach and how to address them: identity, stored fields, tags and communication preferences. Contacts belong to a schema, which defines the additional details your business needs.
You find them under Records in the top navigation, one page per contact schema.
Add contacts
One at a time. On the schema's records page, the Add button (labelled with your schema's name, so Add Student on a Student schema) opens a form with the built-in fields, your own attributes, tags, and the communication topics to put them on.
In bulk, from a CSV. The Import button on the same page takes you there. See below.
From your product. Forms, workflows and the API all write contacts. See the Records API.
Consent is not implied by any of these. A contact created through the API with no subscriptions value is subscribed to email, so if you are loading people who have not opted in, send subscriptions as an empty list. The Add form in the app is the opposite: it only subscribes the contact to email if you put them on at least one topic.
Import a CSV
Open Settings, then Schemas, open the schema and go to the Import tab. Download sample CSV gives you a file with exactly the right column headers.
The standard importer matches headers to writable attribute keys after trimming whitespace. It does not provide a column-mapping screen. Unknown columns, evaluated attributes and computed attributes are omitted. The import tracks skipped fields internally, but the current table shows imported and failed rows rather than a per-column warning. Inspect a few imported profiles to confirm the fields arrived; “Succeeded” alone does not prove your headings matched.
In onboarding, maxinja can instead inspect a CSV sample, propose a mapping and start that mapped import after you review the plan. A sample preview is not validation of every row. See Import a contact list.
Before you upload you can set Tags that are added to every contact in the file, and tick Subscribe to email by default.
Only tick Subscribe to email by default if you have explicit consent to email everyone in the file. It fills the blanks and nothing more: a row carrying its own subscriptions value keeps that value, so consent can vary inside one file. The default reaches only the contacts this import creates, never one it matches to a contact you already have, whose subscriptions stay as they are unless the row says otherwise. Leave the box unticked and a created contact whose subscriptions cell is blank ends up subscribed to nothing.
A file can be up to 50 MB and 500,000 rows. The importer trims cell text and converts supported number, boolean and multi-value fields before validating records. Use true and false for booleans: true, yes, 1 and on are interpreted as true; other nonempty text becomes false, so a misspelling may not fail the row.
Blank cells are omitted. On an existing contact, omission leaves the old value in place; it does not clear it or rerun creation-time autofill. On a new contact, the field's missing-value policy applies. Check Pending, Running, Succeeded, Partial, Failed or Canceled status, then review failure samples and the downloadable log. Canceling does not undo rows already imported.
Consent: who you are allowed to email
Marketing email checks the contact's channel subscription and, when selected for the email, topic membership. These are separate from identity, tags and segment membership. Adding a tag such as newsletter does not by itself subscribe the contact to a communication topic.
Channel is how you reach them. Email is the only one today.
Topic is a category you define, like Product updates or Weekly digest, under Settings, then Topics. Each topic is public or private. Public topics appear on the preferences page so contacts manage them themselves. A private topic stays hidden from anyone you have not put on it.
On the records table you can select contacts and use Subscribe, Unsubscribe, Add topics and Remove topics to change this in bulk. On a single contact's page, Communication topics does the same thing.
The page your contacts see
Every marketing email carries an unsubscribe link to a preferences page maxclicks hosts for you. The link is signed, so the contact never signs in. There they can turn topics on and off, Save preferences, or Unsubscribe from all future emails. A private topic only appears for a contact you have already put on it, and that contact can turn it off from the same page.
Double opt-in
A form can require double opt-in. The submission is held, maxclicks emails a single-use confirmation link, and the contact is only created once that link is clicked. The link works once, and it stops working 24 hours after the submission: a click after that fails, the held submission is dropped, and the person has to submit the form again. Turn it on with Double Opt-in on the form.
The consent record maxclicks keeps for you
Every consent change is logged automatically: subscribes, unsubscribes, topic changes, suppressions, double opt-in confirmations. Each entry keeps what happened, the resulting state, whether it was single or double opt-in, who did it, and the surrounding context including the consent text, source, form and timestamp. The IP address and user agent are recorded with the entry, but they are stripped from the context before it is returned, so the API never hands them back.
You do not have to do anything to get this, and it is what you reach for when someone asks you to prove a person opted in. The trail is read through the Records API rather than shown in the app.
Suppressions: the do-not-contact list
A suppression is a mark on an email address, not on a contact. It covers every contact that shares that address, including ones you create later. Addresses are stored hashed.
| Reason | Where it comes from |
|---|---|
| Unsubscribed | An unsubscribe action |
| Hard bounce or Consecutive soft bounces | Email delivery feedback |
| Spam complaint | Complaint feedback |
| Manual or Legal | A workspace action with a selected reason and optional details |
On a contact's page, Suppress email adds a Manual or Legal suppression. Remove appears for workspace members with write access; removal is also subject to the operation's scope and allowed reasons. The public API has its own removal restrictions, so use its reference rather than assuming every visible UI action is available to an API key.
Marketing email is blocked by any applicable suppression. Transactional email has different consent rules but still checks bounce suppressions. A workspace-scoped suppression affects that workspace, while a platform-scoped suppression can apply more broadly.
Removing a suppression does not itself grant a missing channel subscription or topic membership. Inspect the complete preference state before expecting the next send to qualify.
You can suppress a phone number too, and it is recorded, but nothing acts on it yet: email is the only channel maxclicks sends on.
How maxclicks knows two records are the same person
When a contact arrives, maxclicks looks inside that schema for someone it already has, checking in this order:
- The maxclicks id, when the write carries one.
userId, your own identifier for the person.email, when there is nouserId.phone, when there is neither.
Send the same person twice and you get one contact, not two. The database enforces it: inside one schema, no two contacts share a userId, or an email with no userId, or a phone with neither.
When two contacts want the same address
If a write would hand a contact an identifier that already belongs to somebody else, three routes carry on rather than throwing the whole write away: the upsert endpoint (POST /v1/schemas/{schema}/records/upsert), form submissions and workflow steps. They keep the old identifier, apply everything else you sent, and report the change they had to skip. They try your new values first and fall back to the stored ones until they find a combination nobody else holds.
Everywhere else the conflicting write is not applied at all. Creating a record (POST /v1/schemas/{schema}/records) and patching one (PATCH /v1/schemas/{schema}/records/{id}) both answer with a conflict error and write nothing, and editing the identifier in the app fails the same way. A clashing CSV row is not half-applied either: it is counted as a failed row, with "Conflicting identifiers (email/phone/userId)." against it in the import's failure log, while the rest of the file goes in as usual.
Resolve the source identity before retrying. Compare the two records and decide which identifier belongs to which person; update your upstream mapping and correct the affected records. Do not assume maxclicks will merge profiles, consent or history for you.
Anything you send is saved, not just the identifiers. Sending an email with { email, firstName } updates that contact's first name on the way through.
The fields every contact has
Plus your own attributes.
| Field | Key | Notes |
|---|---|---|
| Full Name | fullName | Built from the first and last name |
| First Name, Last Name | firstName, lastName | |
| Email Address | email | |
| Phone Number | phone | |
| User ID | userId | Your own identifier for the person |
| Avatar URL | avatarUrl | |
| Notes | notes | |
| Tags | tags | |
| Subscriptions | subscriptions | The channels this person has consented to |
| Topic IDs | topicIds | Setting this replaces their current topics rather than adding to them |
| Source | source | Import, API, form, manual, workflow or integration. Set by maxclicks, and you cannot write it |
| Email Statistics | emailStatistics | Sent, delivered, bounced, complained, unsubscribed, unique opens, unique clicks. Set by maxclicks, and you cannot write it |
| Created At, Updated At | createdAt, updatedAt |
Setting one of your own fields to nothing clears its value but keeps the field on the record.
Working through the table
Select rows and maxclicks offers Subscribe, Unsubscribe, Add tags, Add topics, Remove topics and Delete. Search filters the list, and Select Columns decides which fields you see, including your own.
The contact list orders newest records first, with a stable ID tie-break. The first page refreshes the count; subsequent pages reuse that count. Search covers identity text, notes and tags rather than acting as an arbitrary filter over every custom field. Use a segment for a business rule such as renewal date or activity threshold.
Deleting contacts is permanent and asks you to confirm first. Cleanup is an operation that can continue in the background, including associated events and calculated data. The workspace can temporarily reject other processing while that cleanup is active. Wait for the deletion status to complete; the initial acceptance is not proof that every related store has been cleared.
A record opened during review can also contain evaluated relationships and computed attributes. Use Compute value to inspect the field; for a computed attribute this reads a published result, not a fresh event calculation. Check publication status when freshness matters.
Open a contact for their Profile, an Activities timeline (created, updated, suppressed) and Analytics.
Related
- Schemas: defining the shape a contact takes.
- Segments: describe an audience and email it.
- Forms: collect contacts from your site, with consent.
- Records API and Suppressions API: doing all of this from your own code.