Powered by AppSignal & Oban Pro

BentoSdk Subscribers API

livebook/subscribers_api.livemd

BentoSdk Subscribers API

Mix.install([
  {:bento_sdk, "~> 0.1.0"}
])

Introduction

This notebook demonstrates how to use the BentoSdk to work with subscribers in the Bento marketing platform. The Subscribers API allows you to:

  • Find subscribers
  • Create subscribers
  • Find or create subscribers (upsert)
  • Import subscribers in bulk
  • Change subscriber email
  • Subscribe/unsubscribe users

Configuration

We’ll use Livebook’s secrets feature to securely store and access your Bento credentials:

# Configure BentoSdk with the LiveBook secrets
BentoSdk.configure(
  site_uuid: System.fetch_env!("LB_BENTO_SITE_UUID"),
  username: System.fetch_env!("LB_BENTO_USERNAME"),
  password: System.fetch_env!("LB_BENTO_PASSWORD")
)

Finding Subscribers

Let’s find a subscriber by email:

# Example of finding a subscriber
email = "user@example.com"

case BentoSdk.Subscribers.find(email) do
  {:ok, nil} ->
    "Subscriber not found"
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Creating Subscribers

Now let’s create a new subscriber:

# Example of creating a subscriber
email = "new_user@example.com"

case BentoSdk.Subscribers.create(email) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Finding or Creating Subscribers (Upsert)

This is a common operation that will find a subscriber if they exist, or create them if they don’t:

# Example of finding or creating a subscriber
email = "user@example.com"

case BentoSdk.Subscribers.find_or_create(email) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Importing Subscribers in Bulk

You can import multiple subscribers at once:

# Example of importing subscribers in bulk
subscribers = [
  %{
    email: "user1@example.com",
    first_name: "User",
    last_name: "One"
  },
  %{
    email: "user2@example.com",
    first_name: "User",
    last_name: "Two"
  }
]

case BentoSdk.Subscribers.import(subscribers) do
  {:ok, result} ->
    result
  {:error, reason} ->
    "Error: #{reason}"
end

Changing Subscriber Email

You can change a subscriber’s email address:

# Example of changing a subscriber's email
old_email = "old_email@example.com"
new_email = "new_email@example.com"

case BentoSdk.Subscribers.change_email(old_email, new_email) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Updating Subscriber Attributes

You can update a subscriber’s attributes:

# Example of updating a subscriber's attributes
email = "user@example.com"
attributes = %{first_name: "Alan", last_name: "Bradburne"}

case BentoSdk.Subscribers.update(email, attributes) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Subscribe/Unsubscribe

You can subscribe or unsubscribe users:

# Example of subscribing a user
email = "user@example.com"

case BentoSdk.Subscribers.subscribe(email) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end
# Example of unsubscribing a user
email = "user@example.com"

case BentoSdk.Subscribers.unsubscribe(email) do
  {:ok, subscriber} ->
    subscriber
  {:error, reason} ->
    "Error: #{reason}"
end

Example: Creating a Newsletter Signup Flow

Here’s an example of how you might handle a newsletter signup:

# Example of a newsletter signup flow
email = "newsletter_signup@example.com"

# First, check if the subscriber already exists
subscriber_result = BentoSdk.Subscribers.find(email)

case subscriber_result do
  {:ok, nil} ->
    # Subscriber doesn't exist, create a new one
    case BentoSdk.Subscribers.create(email) do
      {:ok, subscriber} ->
        %{
          status: "success",
          message: "Thank you for subscribing to our newsletter!",
          subscriber: subscriber
        }
      {:error, reason} ->
        %{
          status: "error",
          message: "Failed to create subscriber: #{reason}"
        }
    end
  {:ok, existing_subscriber} ->
    # Subscriber exists, update their tags

    # Get existing tags
    cached_tag_ids = existing_subscriber["attributes"]["cached_tag_ids"] || []
    {:ok, tags} = BentoSdk.Tags.get()
    existing_tags = Enum.map(cached_tag_ids, fn tag_id ->
      tags |> Enum.find(fn tag -> tag["id"] == tag_id end) |> Map.get("attributes") |> Map.get("name")
    end)

    new_tags = ["newsletter", "website_signup"] -- existing_tags

    if Enum.empty?(new_tags) do
      %{
        status: "success",
        message: "You are already subscribed to our newsletter.",
        subscriber: existing_subscriber
      }
    else
      # Add the new tags
      case BentoSdk.Subscribers.add_tags(email, new_tags) do
        {:ok, updated_subscriber} ->
          %{
            status: "success",
            message: "Your newsletter preferences have been updated.",
            subscriber: updated_subscriber
          }
        {:error, reason} ->
          %{
            status: "error",
            message: "Failed to update tags: #{reason}"
          }
      end
    end
  {:error, reason} ->
    %{
      status: "error",
      message: "Failed to check subscriber: #{reason}"
    }
end

Example: Bulk Import with Error Handling

Here’s an example of how you might handle a bulk import with error handling:

# Example of bulk import with error handling
subscribers = [
  %{
    email: "valid_user@example.com",
    first_name: "Valid",
    last_name: "User"
  },
  %{
    email: "invalid-email",  # Invalid email format
    first_name: "Invalid",
    last_name: "User"
  },
  %{
    email: "duplicate@example.com",
    first_name: "Duplicate",
    last_name: "User"
  },
  %{
    email: "duplicate@example.com",  # Duplicate email
    first_name: "Another",
    last_name: "Duplicate"
  }
]

# Validate emails before import
validated_subscribers = Enum.filter(subscribers, fn subscriber ->
  email = subscriber.email
  # Simple regex for email validation
  email_regex = ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
  Regex.match?(email_regex, email)
end)

# Remove duplicates by email
unique_subscribers = Enum.uniq_by(validated_subscribers, & &1.email)

# Import the validated and deduplicated subscribers
case BentoSdk.Subscribers.import(unique_subscribers) do
  {:ok, result} ->
    %{
      status: "success",
      total_attempted: length(subscribers),
      total_valid: length(validated_subscribers),
      total_unique: length(unique_subscribers),
      total_imported: result["imported"] || 0,
      invalid_count: length(subscribers) - length(validated_subscribers),
      duplicate_count: length(validated_subscribers) - length(unique_subscribers),
      import_result: result
    }
  {:error, reason} ->
    %{
      status: "error",
      message: "Import failed: #{reason}",
      total_attempted: length(subscribers)
    }
end