Contacts API

The Contacts API allows you to manage contacts in your SMASHSEND account. This API is commonly used by Zapier developers and other integration platforms to sync contact data, create automated workflows, and manage customer information across different systems.

Authentication & Base URL

Base URL: https://api.smashsend.com

All API requests must include your API key in the Authorization header. You can manage your API keys in your SMASHSEND dashboard under Settings → API.

Authorization: Bearer sk_live_abc123...

Rate Limits: API requests are limited to ensure service reliability. For Zapier integrations, we recommend using webhook triggers instead of polling for real-time updates.

Contacts Endpoints

GET/v1/contacts

Lists contacts for the authenticated workspace with cursor-based pagination. Perfect for Zapier “New Contact” triggers and bulk contact synchronization.

Query Parameters

limit - Number of contacts to return (default: 15, max: 100)

cursor - Pagination cursor for getting next page

sort - Sort order: “createdAt.desc” or “createdAt.asc” (default: “createdAt.desc”)

search - Search contacts by email, name, or phone

status - Filter by status: “SUBSCRIBED”, “UNSUBSCRIBED”, or “BANNED”

includeCount - Include total count in response (boolean)

Request

curl "https://api.smashsend.com/v1/contacts?limit=15&sort=createdAt.desc" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "contacts": {
    "cursor": "MjAyNS0wNi0wMlQwMDoyMDoxNS42OTVa",
    "hasMore": true,
    "items": [
      {
        "id": "ctc_L10vZx0SFrmzKkHwWjoLYSIf",
        "createdAt": "2025-06-02T00:35:15.593Z",
        "updatedAt": "2025-06-04T04:13:20.114Z",
        "properties": {
          "avatarUrl": null,
          "birthday": null,
          "city": null,
          "countryCode": "DZ",
          "email": "contact@example.com",
          "firstName": "John",
          "language": "en",
          "lastName": "Doe",
          "phone": "",
          "status": "SUBSCRIBED",
          "timezone": "Africa/Algiers",
          "company": "ACME Corp"
        },
        "workspaceId": "wrk_ENGTK86JEXXckyDVJthKkPer"
      }
    ],
    "totalCount": 11
  }
}

GET/v1/contacts/{contactId}

Retrieves a specific contact by ID. Useful for Zapier “Find Contact” actions and getting detailed contact information.

Path Parameters

contactId - The unique ID of the contact (starts with “ctc_”)

Request

curl "https://api.smashsend.com/v1/contacts/ctc_abc123def456" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "contact": {
    "id": "ctc_E7g1zoXJEAkWvgN0yMEIuPOM",
    "createdAt": "2025-06-04T05:26:19.247Z",
    "updatedAt": "2025-06-04T05:26:20.454Z",
    "properties": {
      "avatarUrl": "https://storage.googleapis.com/zootools-beta/workspaces/wrk_ENGTK86JEXXckyDVJthKkPer/contacts/ctc_E7g1zoXJEAkWvgN0yMEIuPOM/avatar",
      "birthday": null,
      "city": null,
      "countryCode": "US",
      "email": "test2@example.com",
      "firstName": "Test2",
      "language": "en-US",
      "lastName": "User2",
      "phone": null,
      "status": "SUBSCRIBED",
      "timezone": "America/New_York",
      "company": "ACME Corp"
    },
    "workspaceId": "wrk_ENGTK86JEXXckyDVJthKkPer"
  }
}

POST/v1/contacts

Creates a new contact. This is the most common endpoint used by Zapier integrations to add contacts from other platforms like forms, CRMs, and e-commerce systems.

Request Body Parameters

Important: All contact data must be wrapped in a properties object.

properties.email - Contact’s email address (required, must be unique)

properties.firstName - Contact’s first name (optional)

properties.lastName - Contact’s last name (optional)

properties.phone - Contact’s phone number with country code (optional)

properties.avatarUrl - URL to contact’s profile photo (optional)

properties.countryCode - Two-letter ISO country code like “US” (optional)

properties.language - ISO 639-1 language code like “en” or “en-US” (optional)

properties.timezone - IANA timezone identifier like “America/New_York” (optional)

properties.* - Any custom properties as key-value pairs within the properties object

Request

curl -X POST "https://api.smashsend.com/v1/contacts" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "email": "jane.smith@example.com",
      "firstName": "Jane",
      "lastName": "Smith",
      "phone": "+1987654321",
      "countryCode": "US",
      "language": "en-US",
      "timezone": "America/New_York",
      "company": "Zapier Inc",
      "jobTitle": "Developer",
      "leadSource": "zapier_integration"
    }
  }'

Response

{
  "contact": {
    "id": "ctc_B7OSYsnTUVwBPOGrgYTjvSGS",
    "createdAt": "2025-06-04T05:25:54.134Z",
    "updatedAt": null,
    "properties": {
      "avatarUrl": null,
      "birthday": null,
      "city": null,
      "countryCode": null,
      "email": "test@example.com",
      "firstName": "Test",
      "language": null,
      "lastName": "User",
      "phone": null,
      "status": "SUBSCRIBED"
    },
    "workspaceId": "wrk_ENGTK86JEXXckyDVJthKkPer"
  }
}

PUT/v1/contacts/{contactId}

Updates an existing contact. Commonly used in Zapier workflows to sync updated information from other systems.

Request

curl -X PUT "https://api.smashsend.com/v1/contacts/ctc_abc123def456" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "firstName": "John",
      "lastName": "Doe Jr.",
      "company": "New Company Inc",
      "jobTitle": "Senior Manager"
    }
  }'

Response

{
  "contact": {
    "id": "ctc_abc123def456",
    "createdAt": "2024-03-20T12:00:00Z",
    "updatedAt": "2024-03-21T14:45:00Z",
    "properties": {
      "avatarUrl": "https://example.com/avatar.jpg",
      "birthday": null,
      "city": null,
      "countryCode": "US",
      "email": "john.doe@example.com",
      "firstName": "John",
      "language": "en-US",
      "lastName": "Doe Jr.",
      "phone": "+1234567890",
      "status": "SUBSCRIBED",
      "timezone": "America/New_York",
      "company": "New Company Inc",
      "jobTitle": "Senior Manager",
      "leadSource": "website"
    },
    "workspaceId": "wrk_ENGTK86JEXXckyDVJthKkPer"
  }
}

DELETE/v1/contacts/{contactId}

Permanently deletes a contact. Use with caution as this action cannot be undone.

Request

curl -X DELETE "https://api.smashsend.com/v1/contacts/ctc_abc123def456" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "deleted": true
}

GET/v1/contacts/search

Search for contacts by email address. Perfect for Zapier “Find Contact” actions before creating or updating.

Query Parameters

email - Email address to search for (required)

Request

curl "https://api.smashsend.com/v1/contacts/search?email=john.doe@example.com" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "contact": {
    "id": "ctc_abc123def456",
    "createdAt": "2024-03-20T12:00:00Z",
    "updatedAt": "2024-03-20T12:00:00Z",
    "properties": {
      "avatarUrl": null,
      "birthday": null,
      "city": null,
      "countryCode": "US",
      "email": "john.doe@example.com",
      "firstName": "John",
      "language": "en-US",
      "lastName": "Doe",
      "phone": null,
      "status": "SUBSCRIBED",
      "timezone": "America/New_York",
      "company": "ACME Corp"
    },
    "workspaceId": "wrk_ENGTK86JEXXckyDVJthKkPer"
  }
}

Error Handling

The API uses standard HTTP status codes. Common error responses:

400 - Bad Request

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid email format"
}

404 - Not Found

{
  "statusCode": 404,
  "error": "Not Found",
  "message": "Contact not found"
}

409 - Conflict

{
  "statusCode": 409,
  "error": "Conflict", 
  "message": "Contact with this email already exists"
}

Custom Properties

Custom properties allow you to store additional data with contacts. They're commonly used to sync data from CRMs, e-commerce platforms, and other business tools via Zapier.

Before using custom properties in API calls, you need to create them in your SMASHSEND dashboard or via the Contact Properties API. See the Contact Properties API documentation for more details.

Common Custom Property Examples

{
  "properties": {
    "email": "contact@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "ACME Corp",
    "jobTitle": "Marketing Manager",
    "leadSource": "website",
    "industry": "Technology",
    "annualRevenue": "100000",
    "isCustomer": true,
    "lastPurchaseDate": "2024-03-15"
  }
}

CRM Integration Properties

{
  "properties": {
    "email": "contact@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "ACME Corp",
    "jobTitle": "Marketing Manager", 
    "industry": "Technology",
    "annualRevenue": 250000,
    "leadScore": 85,
    "lastContactDate": "2024-03-15",
    "isQualified": true,
    "salesRep": "John Smith",
    "dealStage": "Proposal"
  }
}

E-commerce Integration Properties

{
  "properties": {
    "email": "customer@example.com",
    "firstName": "Jane",
    "lastName": "Smith",
    "customerSince": "2023-06-15",
    "totalOrders": 12,
    "lifetimeValue": 1850.00,
    "preferredCategory": "Electronics",
    "isVip": true,
    "lastPurchaseDate": "2024-03-10",
    "loyaltyTier": "Gold",
    "averageOrderValue": 154.17
  }
}

Form Integration Properties

{
  "properties": {
    "email": "lead@example.com",
    "firstName": "Mike",
    "lastName": "Johnson",
    "formSource": "Contact Us Page",
    "referralSource": "Google Ads",
    "interests": "Email marketing, automation, analytics",
    "budget": "$1000-5000",
    "timeline": "Next 3 months",
    "hasOptedInMarketing": true,
    "companySize": "50-200 employees",
    "currentTool": "Mailchimp"
  }
}

Multi-Select Properties (Tags)

For multi-select properties (like tags or categories), you can send an array of values to completely replace the contact's current values. This makes syncing from external systems straightforward - just send the desired final state.

{
  "properties": {
    "email": "contact@example.com",
    "tags": ["customer", "enterprise", "priority"],
    "interests": ["email-marketing", "automation"],
    "categories": ["premium"]
  }
}

How it works: When you send an array for a multi-select property, SMASHSEND will replace all existing values with the new ones.

Examples:

// Contact currently has tags: ["customer", "trial"]
// You send: { "tags": ["customer", "enterprise"] }
// Result: Contact will have tags: ["customer", "enterprise"]
// ("trial" is removed, "enterprise" is added)

// Contact currently has tags: ["customer", "trial"] 
// You send: { "tags": [] }
// Result: Contact will have no tags (all removed)

// Contact currently has tags: ["customer"]
// You send: { "tags": ["customer"] }
// Result: No change (same values)

Advanced: Granular Control (Add/Remove)

For advanced use cases where you need precise control over what gets added or removed without fetching current values, use the structured format with add and remove arrays:

{
  "properties": {
    "email": "contact@example.com",
    "tags": {
      "add": ["vip", "enterprise"],
      "remove": ["trial", "free"]
    },
    "interests": {
      "add": ["api-access"],
      "remove": []
    }
  }
}

This is useful for webhooks or event-driven updates where you only know what changed, not the complete desired state.

Zapier Integration Tips

1. Use “Search then Create” Pattern

Always search for existing contacts by email before creating new ones to avoid duplicates.

2. Leverage Custom Properties

Use custom properties to sync additional data from your source systems like CRM fields, e-commerce data, or form responses.

3. Handle Rate Limits

For bulk operations, consider using batch endpoints or implementing delays between requests.

4. Use Webhooks for Real-time Updates

Instead of polling for changes, set up webhooks to get notified when contacts are created, updated, or deleted.

Working with Timezones

SMASHSEND supports timezone-aware email delivery to maximize engagement by sending emails at optimal times in each contact's local timezone. The timezone field accepts IANA timezone identifiers and enables smart delivery features.

Timezone Field

Type: String (optional)
Format: IANA timezone identifier (e.g., America/New_York)
Validation: Must be a valid IANA timezone or null
Default: null (will be auto-inferred from countryCode if available)

Supported Timezone Values

SMASHSEND supports all 597 IANA timezone identifiers. Common examples:

North America:

America/New_York        # Eastern Time (UTC-05:00/-04:00)
America/Los_Angeles     # Pacific Time (UTC-08:00/-07:00)
America/Chicago         # Central Time (UTC-06:00/-05:00)
America/Denver          # Mountain Time (UTC-07:00/-06:00)
America/Toronto         # Eastern Time Canada
America/Vancouver       # Pacific Time Canada

Europe:

Europe/London           # Greenwich Mean Time (UTC+00:00/+01:00)
Europe/Paris            # Central European Time (UTC+01:00/+02:00)
Europe/Berlin           # Central European Time (UTC+01:00/+02:00)
Europe/Rome             # Central European Time (UTC+01:00/+02:00)
Europe/Madrid           # Central European Time (UTC+01:00/+02:00)
Europe/Amsterdam        # Central European Time (UTC+01:00/+02:00)

Asia Pacific:

Asia/Tokyo              # Japan Standard Time (UTC+09:00)
Asia/Shanghai           # China Standard Time (UTC+08:00)
Asia/Singapore          # Singapore Standard Time (UTC+08:00)
Asia/Seoul              # Korea Standard Time (UTC+09:00)
Asia/Kolkata            # India Standard Time (UTC+05:30)
Asia/Dubai              # Gulf Standard Time (UTC+04:00)
Australia/Sydney        # Australian Eastern Time (UTC+10:00/+11:00)
Australia/Melbourne     # Australian Eastern Time (UTC+10:00/+11:00)
Pacific/Auckland        # New Zealand Time (UTC+12:00/+13:00)

Getting Timezone Data from Libraries

// Install: npm install moment-timezone
import moment from 'moment-timezone';

// Get all timezone names (597 total)
const allTimezones = moment.tz.names();
console.log(allTimezones); // ["Africa/Abidjan", "Africa/Accra", ...]

// Get popular timezones for dropdowns
const popularTimezones = [
  'America/New_York', 'America/Los_Angeles', 'Europe/London',
  'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
];

// Validate timezone
const isValid = !!moment.tz.zone('America/New_York'); // true
const isInvalid = !!moment.tz.zone('Invalid/Zone'); // false

// Get current offset for timezone
const offset = moment().tz('America/New_York').format('Z'); // "-04:00"

// Browser detection (no library needed)
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(userTimezone); // e.g., "America/New_York"

Usage Examples

Create contact with timezone:

{
  "properties": {
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "timezone": "America/New_York",
    "countryCode": "US",
    "language": "en-US"
  }
}

Update contact timezone:

{
  "properties": {
    "timezone": "Europe/London"
  }
}

Validation & Error Handling

Invalid timezone values return a 400 Bad Request error:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid/Timezone is not a valid IANA timezone identifier"
}

Valid Examples:
“America/New_York”
“Europe/London”
“Asia/Tokyo”
null (will be auto-inferred)

Invalid Examples:
“EST” (use America/New_York instead)
“GMT+5” (use Asia/Kolkata instead)
“Invalid/Zone”

Auto-Detection & Smart Features

Automatic Timezone Inference (Best Effort):
When timezone is not provided but countryCode is available, SMASHSEND automatically infers the most likely timezone as a best effort. However, this may not be accurate for countries with multiple timezones.

⚠️ Important: For optimal email delivery, always provide the explicit timezone when possible. Inference is a fallback feature and may not be accurate for all users.

{
  "properties": {
    "email": "user@example.com",
    "countryCode": "US"  // Will infer "America/New_York" (Eastern Time)
                         // But user might be in Pacific Time!
  }
}

// Better approach - detect timezone explicitly:
{
  "properties": {
    "email": "user@example.com", 
    "countryCode": "US",
    "timezone": "America/Los_Angeles"  // Explicit timezone is more accurate
  }
}

Inference Limitations:
Multi-timezone countries: US has 6+ timezones, Russia has 11
Default bias: We default to the most populous timezone
Border regions: Users near timezone boundaries may be inaccurate
Travelers: Current location may differ from home timezone

Best Practice: Use browser detection or user input to get the actual timezone rather than relying on country-based inference.

Smart Email Delivery:

• Emails are sent during optimal hours (7 AM - 6 PM) in the contact's timezone
• Automatic DST (Daylight Saving Time) handling
• Improved open rates through timezone-aware scheduling
• Better user experience with localized send times

Working with Country Codes

The countryCode field stores the contact's country using ISO 3166-1 alpha-2 country codes.

Country Code Field

Type: String (optional)
Format: Two-letter ISO 3166-1 alpha-2 code (e.g., US, GB, CA)
Validation: Must be a valid ISO country code
Case: Uppercase (automatically normalized)

Common Country Codes

US  # United States
CA  # Canada
GB  # United Kingdom
DE  # Germany
FR  # France
AU  # Australia
JP  # Japan
BR  # Brazil
IN  # India
MX  # Mexico

Getting Country Data from Browser

// Get user's country from locale
const userLocale = navigator.language || navigator.userLanguage;
const countryCode = userLocale.split('-')[1]; // e.g., "en-US" → "US"

// Using Geolocation API (requires user permission)
if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(async (position) => {
    // Use a geocoding service to get country from coordinates
    const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${position.coords.latitude}&longitude=${position.coords.longitude}`);
    const data = await response.json();
    console.log(data.countryCode); // e.g., "US"
  });
}

Working with Language Codes

The language field stores the contact's preferred language using ISO 639-1 language codes.

Language Field

Type: String (optional)
Format: ISO 639-1 two-letter code (e.g., en, es, fr) or locale format (e.g., en-US, pt-BR)
Validation: Must be a valid ISO language code
Case: Lowercase (automatically normalized)

Common Language Codes

en     # English
es     # Spanish
fr     # French
de     # German
it     # Italian
pt     # Portuguese
ja     # Japanese
ko     # Korean
zh     # Chinese
ar     # Arabic
ru     # Russian
hi     # Hindi

Locale Format (with country):

en-US  # English (United States)
en-GB  # English (United Kingdom)
es-ES  # Spanish (Spain)
es-MX  # Spanish (Mexico)
pt-BR  # Portuguese (Brazil)
zh-CN  # Chinese (Simplified)

Getting Language Data from Browser

// Get user's preferred language
const userLanguage = navigator.language || navigator.userLanguage;
console.log(userLanguage); // e.g., "en-US", "es-MX", "fr-FR"

// Extract just the language code
const languageCode = userLanguage.split('-')[0]; // e.g., "en-US" → "en"

// Get all user languages (in order of preference)
const userLanguages = navigator.languages;
console.log(userLanguages); // e.g., ["en-US", "en", "es"]

Complete Integration Example

Here's a complete example showing how to detect and send all geographic/locale data from a web form using explicit detection (recommended):

// RECOMMENDED: Detect user's locale information explicitly
async function createContactWithLocaleData(email, firstName, lastName) {
  // ✅ BEST: Detect timezone explicitly from browser
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  
  // ✅ BEST: Detect language from browser preferences
  const language = navigator.language || navigator.userLanguage;
  
  // ✅ BETTER: Extract country from language if available
  const countryCode = language.includes('-') ? language.split('-')[1] : null;
  
  // Create contact with explicit data (most accurate)
  const response = await fetch('/v1/contacts', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      properties: {
        email: email,
        firstName: firstName,
        lastName: lastName,
        timezone: timezone,           // ✅ Explicit timezone (most accurate)
        language: language,           // ✅ User's actual language preference
        countryCode: countryCode,     // ✅ Detected from locale
        detectionMethod: "browser"    // Custom property for tracking
      }
    })
  });
  
  return response.json();
}

// FALLBACK: If you only have country data
async function createContactWithCountryOnly(email, firstName, lastName, countryCode) {
  const response = await fetch('/v1/contacts', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      properties: {
        email: email,
        firstName: firstName,
        lastName: lastName,
        countryCode: countryCode,     // ⚠️ SMASHSEND will infer timezone (best effort)
        detectionMethod: "country_inference"
      }
    })
  });
  
  return response.json();
}

// Usage examples
createContactWithLocaleData('user@example.com', 'John', 'Doe');  // ✅ Recommended
createContactWithCountryOnly('user@example.com', 'Jane', 'Smith', 'US');  // ⚠️ Fallback

Accuracy Recommendations

1. Always Use Explicit Timezone Detection:
Use Intl.DateTimeFormat().resolvedOptions().timeZone in browsers or equivalent methods in your platform to get the user's actual timezone.

2. Validate Before Sending:
Use timezone libraries to validate timezone identifiers before sending them to SMASHSEND to avoid validation errors.

3. Handle Multi-Timezone Countries:
Countries like the US, Russia, Canada, and Australia have multiple timezones. Always ask users or detect their specific timezone rather than assuming from country alone.

Benefits of Complete Locale Data

Enhanced Personalization:
• Send emails in the contact's preferred language
• Respect cultural preferences and holidays
• Optimize send times for local timezones
• Improve deliverability with localized content

Better Analytics:
• Segment audiences by geography and language
• Track engagement by timezone and country
• Optimize campaigns for different regions
• Understand global customer distribution

Compliance & UX:
• Respect time-based communication preferences
• Comply with regional regulations (GDPR, CAN-SPAM)
• Provide better user experience with localized timing
• Reduce unsubscribes through timezone awareness

Recommended Libraries by Language

JavaScript/TypeScript:
moment-timezone: Full timezone support with DST handling
date-fns-tz: Lightweight timezone utilities
luxon: Modern date/time library with timezone support
Intl.DateTimeFormat: Built-in browser API

Python:
pytz: Comprehensive timezone library
zoneinfo: Built-in timezone support (Python 3.9+)
pendulum: Modern date/time library

Other Languages:
Ruby: tzinfo gem or built-in Time class
PHP: Carbon library or built-in DateTime
Java: java.time package (built-in)
C#: NodaTime library or TimeZoneInfo
Go: time package (built-in) + go-timezone
Rust: chrono-tz crate

Country & Language Detection Libraries

JavaScript:
country-list: ISO country codes and names
i18n-iso-countries: Country code utilities
iso-639-1: Language code utilities
Intl.Locale: Built-in locale handling

GeoIP APIs for Country Detection:
MaxMind GeoIP2: Professional IP geolocation
IPinfo.io: IP geolocation with free tier
BigDataCloud: Free IP geolocation API
ipapi.co: Simple IP geolocation
Browser Geolocation API: GPS-based (requires permission)