Skip to main content
Contacts represent email recipients in your Resend account. This guide covers creating, managing, and organizing contacts using both global contacts and audience-specific contacts.

Overview

The Contacts API supports two types of contacts:

Global Contacts

Modern approach supporting custom properties and segment organization. Recommended for new integrations.

Audience-Specific Contacts

Legacy approach tied to specific audiences. Limited property support.

Global Contacts vs Audience-Specific

Recommendation: Use global contacts for all new integrations. They provide more flexibility with custom properties and segment-based organization.
FeatureGlobal ContactsAudience-Specific
Custom Properties✅ Yes❌ No
Segment Organization✅ Yes❌ No
Topic Subscriptions✅ Yes✅ Yes
API Path/contacts/audiences/{id}/contacts

Creating Contacts

Global Contact

Create a contact without an audience ID to make it globally available.
import (
    "github.com/resend/resend-go/v3"
)

client := resend.NewClient("re_123456789")

params := &resend.CreateContactRequest{
    Email:     "[email protected]",
    FirstName: "John",
    LastName:  "Doe",
}

contact, err := client.Contacts.Create(params)
if err != nil {
    panic(err)
}

fmt.Println("Contact ID:", contact.Id)
Important: The Resend API currently only accepts string values for custom properties. Non-string values (numbers, booleans, etc.) will be rejected.
// ✅ Correct
Properties: map[string]any{
    "age": "30",        // String
    "active": "true",  // String
}

// ❌ Incorrect
Properties: map[string]any{
    "age": 30,         // Number - will fail
    "active": true,    // Boolean - will fail
}

Audience-Specific Contact (Legacy)

Create a contact tied to a specific audience by providing an AudienceId.
Audience-Specific Contact
params := &resend.CreateContactRequest{
    Email:        "[email protected]",
    AudienceId:   "ca4e37c5-a82a-4199-a3b8-bf912a6472aa",
    FirstName:    "Jane",
    LastName:     "Smith",
    Unsubscribed: false,
}

contact, err := client.Contacts.Create(params)
if err != nil {
    panic(err)
}

Custom Properties

Global contacts support custom properties for storing additional metadata. Properties must be defined before use.
1

Define Properties

First, create the property definitions you want to use:
// Define available properties
properties := []struct {
    key string
    typ string
}{
    {"tier", "string"},
    {"role", "string"},
    {"signup_source", "string"},
}

for _, prop := range properties {
    _, err := client.Contacts.Properties.Create(&resend.CreateContactPropertyRequest{
        Key:  prop.key,
        Type: prop.typ,
    })
    if err != nil {
        fmt.Printf("Property '%s' may already exist: %v\n", prop.key, err)
    }
}
2

Use Properties on Contacts

Add the properties when creating or updating contacts:
params := &resend.CreateContactRequest{
    Email:     "[email protected]",
    FirstName: "John",
    LastName:  "Doe",
    Properties: map[string]any{
        "tier":          "premium",
        "role":          "admin",
        "signup_source": "website",
    },
}

contact, err := client.Contacts.Create(params)
3

Access Properties

Properties are returned when retrieving contacts:
contact, err := client.Contacts.Get(&resend.GetContactOptions{
    Id: contactId,
})

if contact.Properties != nil {
    fmt.Printf("Tier: %v\n", contact.Properties["tier"])
    fmt.Printf("Role: %v\n", contact.Properties["role"])
}

Retrieving Contacts

Get a single contact by ID or email address.
options := &resend.GetContactOptions{
    Id: "479e3145-dd38-476b-932c-529ceb705947",
}

contact, err := client.Contacts.Get(options)
if err != nil {
    panic(err)
}

fmt.Printf("%s %s (%s)\n", contact.FirstName, contact.LastName, contact.Email)
You can retrieve contacts by either their ID or email address. Both work interchangeably in the Id field.

Listing Contacts

Retrieve all contacts with optional pagination.
// Omit AudienceId for global contacts
options := &resend.ListContactsOptions{}

contacts, err := client.Contacts.List(options)
if err != nil {
    panic(err)
}

fmt.Printf("Found %d contacts\n", len(contacts.Data))
for _, contact := range contacts.Data {
    fmt.Printf("%s (%s)\n", contact.Email, contact.Id)
}

Updating Contacts

Update contact information including properties and subscription status.
params := &resend.UpdateContactRequest{
    Id:        "479e3145-dd38-476b-932c-529ceb705947",
    FirstName: "Jane",
    LastName:  "Updated",
    Email:     "[email protected]",
}

updated, err := client.Contacts.Update(params)
if err != nil {
    panic(err)
}

fmt.Println("Updated:", updated.Data.Email)
The SetUnsubscribed() method is required to set the unsubscribed status to false due to Go’s zero-value behavior with booleans. This will be fixed in v3 of the SDK.

Deleting Contacts

Remove contacts by ID or email address.
options := &resend.RemoveContactOptions{
    Id: "479e3145-dd38-476b-932c-529ceb705947",
}

removed, err := client.Contacts.Remove(options)
if err != nil {
    panic(err)
}

fmt.Printf("Deleted: %v\n", removed.Deleted)

Nested Services

The Contacts service includes three nested services for advanced functionality:

Topics

Manage contact topic subscriptions for preference-based email campaigns.
Managing Topic Subscriptions
// List topics for a contact
topics, err := client.Contacts.Topics.List(contactId)
if err != nil {
    panic(err)
}

fmt.Printf("Contact has %d topic subscriptions\n", len(topics.Data))

// Update topic subscriptions
updateParams := &resend.UpdateContactTopicsRequest{
    Id: contactId,
    Topics: []resend.TopicSubscriptionUpdate{
        {
            Id:           "topic-123",
            Subscription: "opt_in",
        },
    },
}

updated, err := client.Contacts.Topics.Update(updateParams)
See the Topics example for complete usage patterns.

Segments

Add contacts to segments for targeted campaigns.
Managing Contact Segments
// Add contact to segment
addParams := &resend.AddContactSegmentRequest{
    ContactId: contactId,
    SegmentId: "seg-123",
}

_, err := client.Contacts.Segments.Add(addParams)
if err != nil {
    panic(err)
}

// List contact's segments
listParams := &resend.ListContactSegmentsRequest{
    ContactId: contactId,
}

segments, err := client.Contacts.Segments.List(listParams)
if err != nil {
    panic(err)
}

fmt.Printf("Contact is in %d segment(s)\n", len(segments.Data))

// Remove contact from segment
removeParams := &resend.RemoveContactSegmentRequest{
    ContactId: contactId,
    SegmentId: "seg-123",
}

removed, err := client.Contacts.Segments.Remove(removeParams)

Properties

Define custom property schemas for contacts.
Managing Property Definitions
// Create a property definition
propParams := &resend.CreateContactPropertyRequest{
    Key:  "company_size",
    Type: "string",
}

prop, err := client.Contacts.Properties.Create(propParams)
if err != nil {
    panic(err)
}

// List all property definitions
properties, err := client.Contacts.Properties.List()
if err != nil {
    panic(err)
}

for _, prop := range properties.Data {
    fmt.Printf("%s: %s\n", prop.Key, prop.Type)
}

Complete Example

Here’s a complete workflow demonstrating contact management:
Complete Contact Workflow
package main

import (
    "fmt"
    "os"
    
    "github.com/resend/resend-go/v3"
)

func main() {
    apiKey := os.Getenv("RESEND_API_KEY")
    client := resend.NewClient(apiKey)

    // 1. Define custom properties
    properties := []struct {
        key string
        typ string
    }{
        {"tier", "string"},
        {"role", "string"},
        {"signup_source", "string"},
    }

    for _, prop := range properties {
        _, err := client.Contacts.Properties.Create(&resend.CreateContactPropertyRequest{
            Key:  prop.key,
            Type: prop.typ,
        })
        if err != nil {
            fmt.Printf("Property '%s' may already exist\n", prop.key)
        }
    }

    // 2. Create a global contact with properties
    createParams := &resend.CreateContactRequest{
        Email:     "[email protected]",
        FirstName: "John",
        LastName:  "Doe",
        Properties: map[string]any{
            "tier":          "premium",
            "role":          "admin",
            "signup_source": "website",
        },
    }

    created, err := client.Contacts.Create(createParams)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created contact: %s\n", created.Id)

    // 3. Retrieve and display contact
    contact, err := client.Contacts.Get(&resend.GetContactOptions{
        Id: created.Id,
    })
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("\nContact Details:\n")
    fmt.Printf("Name: %s %s\n", contact.FirstName, contact.LastName)
    fmt.Printf("Email: %s\n", contact.Email)
    if contact.Properties != nil {
        fmt.Printf("Properties: %+v\n", contact.Properties)
    }

    // 4. Create a segment and add contact
    segment, err := client.Segments.Create(&resend.CreateSegmentRequest{
        Name: "Premium Users",
    })
    if err != nil {
        panic(err)
    }

    _, err = client.Contacts.Segments.Add(&resend.AddContactSegmentRequest{
        ContactId: created.Id,
        SegmentId: segment.Id,
    })
    if err != nil {
        panic(err)
    }
    fmt.Println("\nAdded contact to segment")

    // 5. List all global contacts
    contacts, err := client.Contacts.List(&resend.ListContactsOptions{})
    if err != nil {
        panic(err)
    }
    fmt.Printf("\nTotal global contacts: %d\n", len(contacts.Data))

    // 6. Update contact properties
    updateParams := &resend.UpdateContactRequest{
        Id: created.Id,
        Properties: map[string]any{
            "tier": "enterprise",
        },
    }

    updated, err := client.Contacts.Update(updateParams)
    if err != nil {
        panic(err)
    }
    fmt.Printf("\nUpdated contact tier: %v\n", updated.Data.Properties["tier"])

    // 7. Clean up
    _, err = client.Contacts.Remove(&resend.RemoveContactOptions{
        Id: created.Id,
    })
    if err != nil {
        panic(err)
    }
    fmt.Println("\nContact removed")

    _, err = client.Segments.Remove(segment.Id)
    if err != nil {
        panic(err)
    }
    fmt.Println("Segment removed")
}

Best Practices

Use Global Contacts

Prefer global contacts over audience-specific contacts for flexibility and custom properties support.

Define Properties First

Always create property definitions before using them on contacts.

String Values Only

Remember that custom properties currently only accept string values.

Organize with Segments

Use segments instead of audiences to organize contacts into targeted groups.

Next Steps

Segments

Learn how to organize contacts into segments

Broadcasts

Send email campaigns to your contacts

Contact Properties

View the contact properties example

API Reference

View the complete Contacts API reference

Build docs developers (and LLMs) love