--- title: Contacts description: Manage and organize your contacts effectively icon: Users --- Contacts in Plunk represent an individual email recipient. Each contact has an identifier and is linked to an email address. ## Adding contacts Contacts can be added to your Plunk project in several ways: - Using [`/v1/track`](/api-reference/public-api/trackEvent) — tracking an event for a contact that doesn't exist yet automatically creates it (subscribed by default). - Using [`POST /contacts`](/api-reference/contacts/createContact) — create or upsert a single contact. Existing emails are **updated**, not rejected; the response includes a `_meta: { isNew, isUpdate }` block and uses status `201` for new contacts and `200` for updates. - Bulk **CSV import** via `POST /contacts/import` — upload a CSV (≤ 5 MB) and poll `GET /contacts/import/:jobId` for status. The first column must be `email`; any other columns map to keys under `data.*`. - Bulk **subscribe / unsubscribe / delete** via `POST /contacts/bulk-subscribe`, `bulk-unsubscribe`, and `bulk-delete` — each accepts up to 1,000 contact IDs and returns a job ID; poll `GET /contacts/bulk/:jobId` for progress. - Adding emails to a **static segment** with `POST /segments/:id/members` and `createMissing: true` will create any contacts that don't already exist. - Manually through the dashboard. ## Contact Data You can associate custom data with each contact using key-value pairs. This data can be used for segmentation and personalization. ### Data types Contact data types are inferred from the **first non-null value** Plunk sees for a given key in your project: | Type | Description | | ------- | ------------------------------------------------------------------------------------------------------------ | | String | Any text value that doesn't look like a date. | | Number | Numeric values, including integers and floats. | | Boolean | `true` or `false`. | | Date | A string matching `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS[.sss]Z`. The full ISO 8601 form is required for date-aware operators in segments. | The type is sampled per-project on first write — once a key is typed as `number`, subsequent string values for that key are still stored, but the **inferred type** stays `number` for the segment filter UI. If you accidentally mix types, the safest reset is to delete the field via `DELETE /contacts/fields/:field` and re-import with the type you want. If a value doesn't match number, boolean, or the ISO 8601 date format, it's typed as a string. ### Non-persistent values Send a value as `{ value, persistent: false }` to use it once for template rendering without storing it on the contact: ```json { "email": "user@example.com", "data": { "firstName": "Ada", "resetCode": { "value": "ABC123", "persistent": false } } } ``` `firstName` is saved on the contact; `resetCode` is available to the template for this send only and is then discarded. Use this for one-shot values like password reset codes, magic links, and one-time tokens. ### Special value handling When creating or updating contacts, certain values are handled specially: | Value | Behavior | Example | | ------------------- | ------------------------------------------- | ------------------------------------- | | Empty string (`""`) | Ignored - field is not stored or updated | `{ name: "" }` → Field is skipped | | `null` | Delete - field is removed from contact data | `{ name: null }` → Field is deleted | | Other values | Stored/updated normally | `{ name: "John" }` → Stored as "John" | To remove a field from a contact's data, set it to `null` when creating or updating the contact. Empty strings are automatically filtered out and won't overwrite existing data. ### Reserved keys Some keys are reserved by Plunk and managed at the contact level (not inside `data`). You can read them but you can't set them through `data`: | Key | Description | | ------------ | ------------------------------------------------------------------------ | | `email` | The contact's email address | | `createdAt` | Timestamp of when the contact was created | | `updatedAt` | Timestamp of the last update | | `subscribed` | Boolean indicating whether the contact is subscribed to marketing emails | The following keys are also **silently filtered out** of any `data` payload — sending them through `/v1/track`, `POST /contacts`, or `PATCH /contacts/:id` will not store them and will not return an error: `id`, `plunk_id`, `plunk_email`, `unsubscribeUrl`, `subscribeUrl`, `manageUrl` The last three are auto-generated per-recipient at send time and exposed as template variables (see [Templates](/concepts/templates)). ### Special keys | Key | Description | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | locale | The contact's preferred locale in ISO 639 (e.g. 'en', 'fr', 'es'). Specifying the locale field on a contact will override the project-wide locale for contact-facing pages and email footers | ## Subscription State Every contact has a `subscribed` field that determines which types of emails they will receive. A newly created contact is subscribed by default when created via `/v1/track`; contacts created via `POST /contacts` use whatever value you pass (default: `false`). When you update a contact, **omitting `subscribed` keeps the current state** — it is not the same as passing `false`. To change the state, pass an explicit `true` or `false`. Every flip of `subscribed` automatically tracks an event on the contact: - `subscribed` flipped to `true` → `contact.subscribed` event - `subscribed` flipped to `false` → `contact.unsubscribed` event These events fire on every path that changes subscription state — manual edits, the public unsubscribe page, bulk operations, automatic unsubscribes from bounces / complaints. You can branch on them in workflows. ### How contacts become unsubscribed A contact can become unsubscribed in several ways: - **Manually** through the dashboard or via the API. - **Self-service** by clicking the unsubscribe link in an email (powered by the public unsubscribe pages). - **Automatically** on a permanent (hard) email bounce. Soft bounces don't change subscription state. - **Automatically** when a recipient marks one of your emails as spam (their mailbox provider reports the complaint back to Plunk). ### Emails by subscription state The subscription state controls whether a contact receives marketing emails. Transactional emails are always delivered regardless of subscription state. | Email type | Subscribed | Unsubscribed | | ------------------------------------------------------------------------------------ | ---------- | ------------- | | **Transactional** (via [/v1/send](/api-reference/public-api/sendTransactionalEmail)) | Delivered | Delivered | | **Campaigns** (marketing) | Delivered | Not delivered | | **Campaigns** (headless) | Delivered | Not delivered | | **Campaigns** (transactional) | Delivered | Delivered | | **Automations** (marketing template) | Delivered | Not delivered | | **Automations** (headless template) | Delivered | Not delivered | | **Automations** (transactional template) | Delivered | Delivered | Even when using the transactional API endpoint (`/v1/send`), you cannot send a marketing template to an unsubscribed contact. Use a transactional template instead if the email must reach unsubscribed contacts. ## What's next How to set, type, and clean up arbitrary contact data. Bulk-load contacts and their custom fields. Group contacts dynamically or statically for targeting. Hosted unsubscribe / preferences pages and the URL template variables.