437 lines
9.6 KiB
Plaintext
437 lines
9.6 KiB
Plaintext
---
|
|
title: Contacts
|
|
description: Manage your audience at scale
|
|
---
|
|
|
|
## What are contacts
|
|
|
|
Contacts are people in your audience. Each contact has an email address, subscription status, and custom data fields you define. Use contacts to personalize emails, build segments, and track engagement.
|
|
|
|
## Creating contacts
|
|
|
|
### Add a single contact
|
|
|
|
```bash
|
|
curl -X POST {{API_URL}}/contacts \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "[email protected]",
|
|
"subscribed": true,
|
|
"data": {
|
|
"firstName": "Sarah",
|
|
"plan": "pro",
|
|
"signupDate": "2024-03-15"
|
|
}
|
|
}'
|
|
```
|
|
|
|
### Automatic upsert
|
|
|
|
If the email already exists, the contact is updated instead of creating a duplicate:
|
|
|
|
```javascript
|
|
// First call - creates contact
|
|
POST /contacts { email: '[email protected]', data: { plan: 'free' } }
|
|
|
|
// Second call - updates same contact
|
|
POST /contacts { email: '[email protected]', data: { plan: 'pro' } }
|
|
|
|
// Result: One contact with plan: 'pro'
|
|
```
|
|
|
|
This is useful when syncing user data from your application.
|
|
|
|
## Contact data fields
|
|
|
|
Store custom data in the `data` field. Use it for:
|
|
|
|
- User profile (name, company, role)
|
|
- Subscription info (plan, MRR, renewal date)
|
|
- Behavior tracking (last login, feature usage)
|
|
- Preferences (newsletter, notifications)
|
|
|
|
**Example:**
|
|
|
|
```json
|
|
{
|
|
"email": "[email protected]",
|
|
"subscribed": true,
|
|
"data": {
|
|
"firstName": "Sarah",
|
|
"lastName": "Chen",
|
|
"company": "Acme Inc",
|
|
"plan": "premium",
|
|
"mrr": 99,
|
|
"lastLoginAt": "2024-03-15T10:30:00Z",
|
|
"preferences": {
|
|
"newsletter": true,
|
|
"productUpdates": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Best practices
|
|
|
|
**Use consistent naming** — Pick camelCase or snake_case and stick with it.
|
|
|
|
**Store dates as ISO strings** — `"2024-03-15T10:30:00Z"` enables date range filtering in segments.
|
|
|
|
**Keep it relatively flat** — Nested objects work, but flat structures are easier to query in segments.
|
|
|
|
**Use numbers for numeric data** — Store `99` not `"99"` to enable greater than/less than comparisons.
|
|
|
|
## Using contact data in emails
|
|
|
|
### Template variables
|
|
|
|
Access contact data in email templates using `{{variableName}}` syntax:
|
|
|
|
```html
|
|
<h1>Hello {{firstName}}!</h1>
|
|
<p>Your {{plan}} plan renews on {{renewalDate}}.</p>
|
|
<p>Total: ${{mrr}}</p>
|
|
```
|
|
|
|
When sending, contact data automatically populates variables:
|
|
|
|
```bash
|
|
curl -X POST {{API_URL}}/v1/send \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"to": "[email protected]",
|
|
"subject": "Renewal reminder",
|
|
"body": "<p>Hi {{firstName}}, your {{plan}} plan renews soon.</p>"
|
|
}'
|
|
```
|
|
|
|
The `firstName` and `plan` values come from the contact's `data` field.
|
|
|
|
### Fallback values
|
|
|
|
Provide defaults when data might be missing:
|
|
|
|
```html
|
|
<p>Hello {{firstName ?? 'there'}}!</p>
|
|
<p>Plan: {{plan ?? 'Free'}}</p>
|
|
```
|
|
|
|
If `firstName` is not set, displays "Hello there!" instead of blank.
|
|
|
|
### Passing additional data
|
|
|
|
Send extra data for a specific email without saving it to the contact:
|
|
|
|
```bash
|
|
curl -X POST {{API_URL}}/v1/send \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"to": "[email protected]",
|
|
"subject": "Your verification code",
|
|
"body": "<p>Your code: {{verificationCode}}</p>",
|
|
"data": {
|
|
"verificationCode": "ABC123"
|
|
}
|
|
}'
|
|
```
|
|
|
|
The `verificationCode` is used in the email but not saved to the contact. This is useful for:
|
|
- One-time codes (password reset, verification)
|
|
- Session-specific data
|
|
- Temporary discount codes
|
|
- Order-specific details
|
|
|
|
### Reserved variables
|
|
|
|
These are always available in templates:
|
|
|
|
- `{{email}}` — Contact email address
|
|
- `{{id}}` — Contact ID
|
|
|
|
Example:
|
|
```html
|
|
<p>Your account: {{email}}</p>
|
|
<p><a href="https://app.example.com/contacts/{{id}}">Manage preferences</a></p>
|
|
```
|
|
|
|
## Listing contacts
|
|
|
|
### Get all contacts
|
|
|
|
```bash
|
|
curl -X GET "{{API_URL}}/contacts?limit=50" \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
Returns:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"items": [...],
|
|
"nextCursor": "abc123",
|
|
"hasMore": true,
|
|
"total": 10000
|
|
}
|
|
}
|
|
```
|
|
|
|
### Pagination
|
|
|
|
For large lists, use cursor-based pagination:
|
|
|
|
```javascript
|
|
let allContacts = [];
|
|
let cursor = null;
|
|
|
|
do {
|
|
const params = new URLSearchParams({ limit: 100 });
|
|
if (cursor) params.append('cursor', cursor);
|
|
|
|
const response = await fetch(`{{API_URL}}/contacts?${params}`, {
|
|
headers: { 'Authorization': `Bearer ${PLUNK_SECRET_KEY}` }
|
|
});
|
|
|
|
const { data } = await response.json();
|
|
allContacts.push(...data.items);
|
|
cursor = data.nextCursor;
|
|
} while (cursor);
|
|
```
|
|
|
|
### Filter by subscription
|
|
|
|
```bash
|
|
# Only subscribed
|
|
curl -X GET "{{API_URL}}/contacts?subscribed=true" \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
|
|
# Only unsubscribed
|
|
curl -X GET "{{API_URL}}/contacts?subscribed=false" \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
### Search by email
|
|
|
|
```bash
|
|
curl -X GET "{{API_URL}}/contacts?search=sarah" \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
Searches for emails containing "sarah".
|
|
|
|
## Getting a contact
|
|
|
|
### By ID
|
|
|
|
```bash
|
|
curl -X GET {{API_URL}}/contacts/contact_id \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
## Updating contacts
|
|
|
|
### Update contact data
|
|
|
|
```bash
|
|
curl -X PATCH {{API_URL}}/contacts/contact_id \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"subscribed": true,
|
|
"data": {
|
|
"plan": "premium",
|
|
"mrr": 99
|
|
}
|
|
}'
|
|
```
|
|
|
|
### Data merging
|
|
|
|
Updates merge with existing data:
|
|
|
|
```javascript
|
|
// Current contact data
|
|
{ "firstName": "Sarah", "company": "Acme" }
|
|
|
|
// Update with
|
|
{ "lastName": "Chen", "plan": "pro" }
|
|
|
|
// Result
|
|
{ "firstName": "Sarah", "company": "Acme", "lastName": "Chen", "plan": "pro" }
|
|
```
|
|
|
|
To remove a field, set it to `null`.
|
|
|
|
### Change subscription status
|
|
|
|
```bash
|
|
curl -X PATCH {{API_URL}}/contacts/contact_id \
|
|
-H "Authorization: Bearer sk_your_secret_key" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"subscribed": false}'
|
|
```
|
|
|
|
**Marketing templates** only send to subscribed contacts. **Transactional templates** send to everyone, regardless of subscription status.
|
|
|
|
## Deleting contacts
|
|
|
|
```bash
|
|
curl -X DELETE {{API_URL}}/contacts/contact_id \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
**Warning:** Deletion is permanent. Consider unsubscribing instead of deleting.
|
|
|
|
## Bulk operations
|
|
|
|
### Import from CSV
|
|
|
|
Prepare a CSV file:
|
|
|
|
```csv
|
|
email,firstName,lastName,plan
|
|
[email protected],Sarah,Chen,pro
|
|
[email protected],John,Doe,free
|
|
```
|
|
|
|
Upload via dashboard:
|
|
1. Go to **Contacts**
|
|
2. Click **Import CSV**
|
|
3. Upload file
|
|
4. Map columns
|
|
5. Set default subscription status
|
|
6. Import
|
|
|
|
The import runs in the background. You'll receive a summary when complete.
|
|
|
|
## Available fields
|
|
|
|
### Get all custom fields
|
|
|
|
See what data fields your contacts have:
|
|
|
|
```bash
|
|
curl -X GET {{API_URL}}/contacts/fields \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
Returns unique field names across all contacts:
|
|
|
|
```json
|
|
{
|
|
"fields": [
|
|
"firstName",
|
|
"lastName",
|
|
"company",
|
|
"plan",
|
|
"mrr",
|
|
"signupDate"
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get field values
|
|
|
|
See all unique values for a specific field:
|
|
|
|
```bash
|
|
curl -X GET {{API_URL}}/contacts/fields/plan/values \
|
|
-H "Authorization: Bearer sk_your_secret_key"
|
|
```
|
|
|
|
Returns:
|
|
|
|
```json
|
|
{
|
|
"values": ["free", "pro", "premium", "enterprise"]
|
|
}
|
|
```
|
|
|
|
Useful for building segment filters and understanding your data.
|
|
|
|
## Syncing with your app
|
|
|
|
Keep contacts in sync with your user database:
|
|
|
|
```javascript
|
|
// When user signs up
|
|
async function onUserSignup(user) {
|
|
await fetch('{{API_URL}}/contacts', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
email: user.email,
|
|
subscribed: true,
|
|
data: {
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
signupDate: new Date().toISOString()
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
// When user updates profile
|
|
async function onUserUpdate(user) {
|
|
await fetch(`{{API_URL}}/contacts/${user.contactId}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
data: {
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
company: user.company
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
// When user subscribes to plan
|
|
async function onSubscriptionChange(user, plan, mrr) {
|
|
await fetch(`{{API_URL}}/contacts/${user.contactId}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
data: {
|
|
plan,
|
|
mrr,
|
|
subscriptionDate: new Date().toISOString()
|
|
}
|
|
})
|
|
});
|
|
}
|
|
```
|
|
|
|
## Best practices
|
|
|
|
**Sync critical data only** — Don't sync every field. Focus on data used in segments, workflows, and personalization.
|
|
|
|
**Use webhooks for real-time sync** — Update contacts immediately when user data changes.
|
|
|
|
**Track subscription separately** — Use the `subscribed` field for email preferences, not app subscription status.
|
|
|
|
**Clean your list regularly** — Remove or unsubscribe bounced and inactive contacts.
|
|
|
|
**Respect opt-outs** — When users unsubscribe, update immediately. Don't re-subscribe them automatically.
|
|
|
|
**Test with real emails** — Use your own email addresses to test the contact experience.
|
|
|
|
## Next Steps
|
|
|
|
- [Build segments](/guides/segments) to group contacts
|
|
- [Send campaigns](/guides/campaigns) to your contacts
|
|
- [Track events](/guides/events) to update contact data automatically
|