defmodule Mailchimp.List do
alias HTTPoison.{Error, Response}
alias Mailchimp.HTTPClient
alias Mailchimp.Link
alias Mailchimp.Member
alias Mailchimp.List.InterestCategory
@moduledoc """
Your Mailchimp list, also known as your audience, is where you store and manage all of your contacts.
### Struct Fields
* `id` - A string that uniquely identifies this list.
* `name` - The name of the list.
* `permission_reminder` - The [persmission reminder](https://mailchimp.com/help/edit-the-permission-reminder/) for the list.
* `campaign_defaults` - Default values for campaigns created for this list.
* `notify_on_subscribe` - The email address to send subscribe notifications to.
* `notify_on_unsubscribe` - The email address to send unsubscribe notifications to.
* `list_rating` - An auto-generated activity score for the list (0-5).
* `email_type_option` - Whether the list support [multiple formats](https://mailchimp.com/help/change-audience-name-defaults/) for emails. When set to true, subscribers can choose whether they want to receive HTML or plain-text emails. When set to false, subscribers will receive HTML emails, with a plain-text alternative backup.
* `subscribe_url_short` - url [Link shortened version](https://mailchimp.com/help/share-your-signup-form/) of this list's subscribe form.
* `subscribe_url_long` - The full version of this list's subscribe form (host will vary).
* `beamer_address` - The list's [Email Beamer](https://mailchimp.com/help/use-email-beamer-to-create-a-campaign/) address.
* `visibility` - Legacy. visibility settings are no longer used Possible values: "pub" or "prv".
* `modules` - Any list-specific modules installed for this list.
* `stats` - Stats for the list. Many of these are cached for at least five minutes.
* `links` - A list of `Mailchimp.Link` types and descriptions for the API schema documents.
"""
defstruct [
id: nil,
name: nil,
contact: nil,
permission_reminder: nil,
use_archive_bar: nil,
campaign_defaults: nil,
notify_on_subscribe: nil,
notify_on_unsubscribe: nil,
list_rating: nil,
email_type_option: nil,
subscribe_url_short: nil,
subscribe_url_long: nil,
beamer_address: nil,
visibility: nil,
modules: [],
stats: nil,
links: []
]
@doc """
Generates an `Mailchimp.List` struct from the given attributes.
"""
def new(attributes) do
%__MODULE__{
id: attributes[:id],
name: attributes[:name],
contact: attributes[:contact],
permission_reminder: attributes[:permission_reminder],
use_archive_bar: attributes[:use_archive_bar],
campaign_defaults: attributes[:campaign_defaults],
notify_on_subscribe: attributes[:notify_on_subscribe],
notify_on_unsubscribe: attributes[:notify_on_unsubscribe],
list_rating: attributes[:list_rating],
email_type_option: attributes[:email_type_option],
subscribe_url_short: attributes[:subscribe_url_short],
subscribe_url_long: attributes[:subscribe_url_long],
beamer_address: attributes[:beamer_address],
visibility: attributes[:visibility],
modules: attributes[:modules],
stats: attributes[:stats],
links: Link.get_links_from_attributes(attributes)
}
end
@doc """
Fetches a list of `Mailchimp.Member` of a given list
"""
def members(%__MODULE__{links: %{"members" => %Link{href: href}}}, query_params \\ %{}) do
case HTTPClient.get(href, [], params: query_params) do
{:ok, %Response{status_code: 200, body: body}} ->
{:ok, Enum.map(body.members, &Member.new(&1))}
{:ok, %Response{status_code: _, body: body}} ->
{:error, body}
{:error, %Error{reason: reason}} ->
{:error, reason}
end
end
def permanently_delete_member(%__MODULE__{links: %{"members" => %Link{href: href}}}, email) do
subscriber_id =
email
|> String.downcase()
|> md5
{:ok, response} =
HTTPClient.post(href <> "/#{subscriber_id}/actions/delete-permanent", Jason.encode!(%{}))
case response do
%Response{status_code: 204} ->
{:ok, email}
%Response{status_code: _, body: body} ->
{:error, body}
end
end
@doc """
Same as `members/2`
but raises errors.
"""
def members!(list, query_params \\ %{}) do
{:ok, members} = members(list, query_params)
members
end
@doc """
Fetches all members on the mailchimp list, and compares then to the given list of members.
Warning: This method only checks for the same email address
Raises errors on connection failure
## Examples
iex> check_diff_to_mailchimp!(list,[member1])
%{members_on_mailchimp: [], members_not_on_mailchimp: []}
"""
def check_diff_to_mailchimp!(list, members) do
members_on_mailchimp =
list
|> members!()
%{
members_not_on_mailchimp:
members
|> Enum.filter(
fn member ->
members_on_mailchimp
|> Enum.find(
fn member_on_mail_chimp ->
member_on_mail_chimp.email_address == member.email_address
end
) == nil
end),
members_only_on_mailchimp:
members_on_mailchimp
|> Enum.filter(
fn member ->
members
|> Enum.find(
fn member_on_mail_chimp ->
member_on_mail_chimp.email_address == member.email_address
end
) == nil
end)
}
end
@doc """
Fetches a `Mailchimp.Member` of a given list by it's email
"""
def get_member(%__MODULE__{links: %{"members" => %Link{href: href}}}, email) do
subscriber_id =
email
|> String.downcase()
|> md5
{:ok, response} = HTTPClient.get(href <> "/#{subscriber_id}")
case response do
%Response{status_code: 200, body: body} ->
{:ok, Member.new(body)}
%Response{status_code: _, body: body} ->
{:error, body}
end
end
@doc """
Same as `get_member`
but raises errors.
"""
def get_member!(list, email) do
{:ok, member} = get_member(list, email)
member
end
@doc """
Sends a request that removes a `Mailchimp.Member` of a given list by it's email
"""
def destroy_member(%__MODULE__{links: %{"members" => %Link{href: href}}}, email) do
subscriber_id =
email
|> String.downcase()
|> md5
{:ok, response} = HTTPClient.delete(href <> "/#{subscriber_id}")
case response do
%Response{status_code: 204} ->
{:ok, email}
%Response{status_code: _, body: body} ->
{:error, body}
end
end
@doc """
Fetches a list of `Mailchimp.List.InterestCategory` of a given list
"""
def interest_categories(%__MODULE__{links: %{"interest-categories" => %Link{href: href}}}) do
{:ok, response} = HTTPClient.get(href)
case response do
%Response{status_code: 200, body: body} ->
{:ok, Enum.map(body.categories, &InterestCategory.new(&1))}
%Response{status_code: _, body: body} ->
{:error, body}
end
end
@doc """
Same as `interest_categories`
but raises errors.
"""
def interest_categories!(list) do
{:ok, categories} = interest_categories(list)
categories
end
@doc """
Creates or updates a member at Mailchimp. You can pass merge_fields or additional_data
"""
def create_or_update_member(
%__MODULE__{links: %{"members" => %Link{href: href}}},
email_address,
status_if_new,
merge_fields \\ %{},
additional_data \\ %{}
)
when is_binary(email_address) and is_map(merge_fields) and
status_if_new in ["subscribed", "pending", "unsubscribed", "cleaned"] do
subscriber_id =
email_address
|> String.downcase()
|> md5
data =
Map.merge(additional_data, %{
email_address: email_address,
status_if_new: status_if_new,
merge_fields: merge_fields
})
case HTTPClient.put(href <> "/#{subscriber_id}", Jason.encode!(data)) do
{:ok, %Response{status_code: 200, body: body}} ->
{:ok, Member.new(body)}
{:ok, %Response{status_code: _, body: body}} ->
{:error, body}
{:error, error} ->
{:error, error}
end
end
@doc """
Same as `create_or_update_member/5`
but raises errors.
"""
def create_or_update_member!(list, email_address, status_if_new, merge_fields \\ %{}, additional_data \\ %{}) do
{:ok, member} = create_or_update_member(list, email_address, status_if_new, merge_fields, additional_data)
member
end
@doc """
Creates a member at Mailchimp. You can pass merge_fields or additional_data
"""
def create_member(
%__MODULE__{links: %{"members" => %Link{href: href}}},
email_address,
status,
merge_fields \\ %{},
additional_data \\ %{}
)
when is_binary(email_address) and is_map(merge_fields) and
status in ["subscribed", "pending", "unsubscribed", "cleaned"] do
case HTTPClient.get(href) do
{:ok, %Response{status_code: 200, body: body}} ->
links = Link.get_links_from_attributes(body)
href = links["create"].href
data =
Map.merge(additional_data, %{
email_address: email_address,
status: status,
merge_fields: merge_fields
})
case HTTPClient.post(href, Jason.encode!(data)) do
{:ok, %Response{status_code: 200, body: body}} ->
{:ok, Member.new(body)}
{:ok, %Response{status_code: _, body: body}} ->
{:error, body}
{:error, error} ->
{:error, error}
end
{:ok, %Response{status_code: _, body: body}} ->
{:error, body}
{:error, error} ->
{:error, error}
end
end
@doc """
Same as `create_member/5`
but raises errors.
"""
def create_member!(list, email_address, status, merge_fields \\ %{}, additional_data \\ %{}) do
{:ok, member} = create_member(list, email_address, status, merge_fields, additional_data)
member
end
@doc """
Batch subscribe members. Pass the List and the list of members with properties
such as email_address, status, and merge_fields (for example, for first and last name).
Additional options can be passed, such as update_existing. See the API docs for details:
https://mailchimp.com/developer/api/marketing/lists/batch-subscribe-or-unsubscribe/
"""
def batch_subscribe(
%__MODULE__{links: %{"self" => %Link{href: href}}},
members,
opts \\ %{}
) do
# default options
opts = Map.merge(%{update_existing: false}, opts)
members = Enum.map(members, fn member -> Map.merge(%{status: "subscribed"}, member) end)
data = Map.merge(opts, %{members: members})
case HTTPClient.post(href, Jason.encode!(data)) do
{:ok, %Response{status_code: 200, body: body}} ->
body = body |> map_members(:new_members) |> map_members(:updated_members)
{:ok, body}
{:ok, %Response{status_code: _, body: body}} ->
{:error, body}
{:error, error} ->
{:error, error}
end
end
@doc """
Same as `batch_subscribe`
but raises errors.
"""
def batch_subscribe!(list, members, opts \\ %{}) do
{:ok, members} = batch_subscribe(list, members, opts)
members
end
@doc """
Creates a list of members at Mailchimp. You can pass merge_fields or additional_data
"""
def create_members(
list,
email_addresses,
status,
merge_fields \\ %{},
additional_data \\ %{}
) do
members =
for email_address <- email_addresses do
%{
email_address: email_address,
status: status,
merge_fields: merge_fields
}
end
case batch_subscribe(list, members, additional_data) do
{:ok, response} ->
{:ok, response.new_members}
{:error, error} ->
{:error, error}
end
end
@doc """
Same as `create_members/5`
but raises errors.
"""
def create_members!(list, email_addresses, status, merge_fields \\ %{}, additional_data \\ %{}) do
{:ok, members} = create_members(list, email_addresses, status, merge_fields, additional_data)
members
end
defp map_members(body, key) do
members =
Map.get(body, key, [])
|> Enum.map(fn member -> Member.new(member) end)
Map.put(body, key, members)
end
defp md5(string) do
:crypto.hash(:md5, string)
|> Base.encode16()
end
end