Categorygithub.com/erikrasmusnilsson/go-mailchimp
repositorypackage
1.3.1
Repository: https://github.com/erikrasmusnilsson/go-mailchimp.git
Documentation: pkg.go.dev

# README

go-mailchimp

This is a simple package that wraps the v3 MailChimp Marketing API. The current usage of this module is quite specific, hence the lack of support for the entire marketing API. The supported features include CRUD operations on lists (or audiences) and batch creation/updating of members within a particular list.

Creating a client

The client is a data structure with receiver functions for communicating with the MailChimp Marketing API. To create a new client, all you need is the API key you wish to use as well as the region of your MailChimp account. After the client has been created, it does not require any closing as it does not keep a constant connection to MailChimp. Rather, it sends separate HTTP requests for each operation the client performs.

chimp := mailchimp.NewClient("key", "region")

For information regarding how to generate an API key and find your region, please refer to the MailChimp documentation.

Ping MailChimp

To make sure that the client is properly set up, you can use the Ping receiver function. This returns an error if something went wrong whilst sending the ping, examples of what could go wrong is a loss in internet connectivity or an invalid API key. If the returned error is nil, then everything is ready to go with the client.

chimp := mailchimp.NewClient("key", "region")
if err := chimp.Ping(); err != nil {
    return handleErr(err)
}

Creating a list (audience)

To create a list, start with initialising a list builder like so:

builder := mailchimp.ListBuilder{}

You can then use chaining receiver functions on the builder to set the parameters of the new list. There are a few required fields for lists, if these are not specified when Build is called, then an error will be returned. It is for this reason that using the ListBuilder is advised, it will ensure that all the required data has been filled in before contacting MailChimp. The required fields are listed below.

  • builder.Name("The name of your list")
  • builder.PermissionReminder("The permission reminder for your list")

Moreover, a list must also contain a contact and campaign defaults. These are instantiated manually. One example for each is given below, together with how to wire them up to the list builder. If any of the fields marked with a required comment are not specified when Build is called, then an error will be returned.

contact := mailchimp.Contact{
    Address1: "Company address",    // required
    Zip: "123 45",                  // required
    City: "Company city",           // required
    State: "Company state",         // required
    Country: "Company country",     // required
    Address2: "Secondary address",
    Company: "Company name",        // required
}

campaignDefaults := mailchimp.CampaignDefaults{
    FromName: "Name",               // required
    FromEmail: "[email protected]",    // required
    Subject: "A subject",           // required
    Language: "Some language",      // required
}

list, err := mailchimp.ListBuilder{}.
    Name("Test").
    PermissionReminder("This is a test").
    Contact(contact).
    CampaignDefaults(campaignDefaults).
    Build()

After a list has been built with the list builder, it can be sent through the client to MailChimp in the CreateList receiver function. This function will return an error either if the MailChimp response is an error or if the MailChimp response could not be unmarshalled. If no error occurs, then the newly created list will be returned to the caller with some additional information filled in by MailChimp, such as the ID of the list.

list, err := mailchimp.ListBuilder{}.[...].Build()
if err != nil {
    return handleErr(err)
}
chimp := mailchimp.NewClient("key", "region")
createdList, err := chimp.CreateList(list)
if err != nil {
    return handleErr(err)
}

Fetching lists

It is possible to fetch all the lists on your MailChimp account using the client receiver function FetchLists. This returns a slice of List structs or an error if MailChimp responds with an error or if the response could not be unmarshalled.

chimp := mailchimp.NewClient("key", "region")
lists, err := chimp.FetchLists()

Fetching a single list

It is also possible to fetch a single list, given that its ID is known beforehand. This can be achieved using the FetchList client receiver function. This function returns both a list and an error, but the error will only be non-nil if an error was returned from the MailChimp Marketing API or if there was an issue in unmarshalling the response.

chimp := mailchimp.NewClient("key", "region")
list, err := chimp.FetchList("list-id")

Updating an existing list

To update the information regarding an existing list, the clients UpdateList receiver function can be used. This, of course, requires knowledge of the lists ID. Even though you can call UpdateList directly with a list struct, it would be advisable to first fetch the list from MailChimp, perform the necessary modifications, and then use that list object to perform the update. The suggested flow is shown below. Note that the updated list will be returned from the UpdateList call together with a potential error.

chimp := mailchimp.NewClient("key", "region")
list, err := chimp.FetchList("list-id")
if err != nil {
    return handleErr(err)
}
list.Name = "New and improved name"
updatedList, err := chimp.UpdateList("list-id", list)

Deleting a list

In order to delete a list from your MailChimp account, you must know the ID of the list beforehand. Once the ID has been acquired, the clients DeleteList receiver function can be invoked to perform the action. This function returns an error if an error was returned from MailChimp.

chimp := mailchimp.NewClient("key", "region")
err := chimp.DeleteList("list-id")

Adding members to a list

There are two ways in which members can be added to a list. Both are described below, but first we will cover how to create new member structs.

Creating a member

To create a member, there is another builder that can be used. Much like the ListBuilder, the MemberBuilder has a required field, namely EmailAddress. For MailChimp merge fields, such as FNAME and PHONE, one can use the MergeField receiver function on the MemberBuilder. A simple example of its usage is shown below.

member, err := mailchimp.MemberBuilder{}.
    EmailAddress("[email protected]").
    StatusSubscribed().
    MergeField("FNAME", "Test").
    MergeField("LNAME", "Smith").
    Build()

The available statuses for members are listed as the corresponding receiver function below.

  • builder.StatusSubscribed()
  • builder.StatusUnsubscribed()
  • builder.StatusPending()
  • builder.StatusCleaned()

Batch

Using Batch to add members will only work if all the members are new. Meaning, you cannot update an existing member if the Batch function is used. The prerequisite knowledge to use Batch is the ID of the list that the members should be added to as well as the members that should be added. Please note that a maximum of 500 members can be batched for a single request as per MailChimps' specifications, if any more than that is sent to Batch then an error will be returned. A simple usage example for the Batch function is shown below.

chimp := mailchimp.NewClient("key", "region")
members := createMembers()
if err := chimp.Batch("list-id", members); err != nil {
    return handleErr(err)
}

BatchWithUpdate

The BatchWithUpdate function is very similar to Batch, with the difference being that BatchWithUpdate will update already existing members of the MailChimp list. Hence, if a member was subscribed with a Batch call, then if the same email address is found with a BatchWithUpdate call but with a status of unsubscribed then the member will be unsubscribed from the list.

chimp := mailchimp.NewClient("key", "region")
members := createMembers()
if err := chimp.BatchWithUpdate("list-id", members); err != nil {
    return handleErr(err)
}

Update a member

Since most of MailChimp's identification is dependent on the members email address, it can be difficult to update this in a batch call. You can therefore perform such an operation using the UpdateMember method.

chimp := mailchimp.NewClient("key", "region")
oldEmailAddress := "[email protected]"
member := createMember() // member with updated email address
if err := chimp.UpdateMember("list-id", oldEmailAddress, member); err != nil {
    return handleErr(err)
}

Archiving a member from a list

First of all, make sure that you actually want to delete the member and not unsubscribe them.

To archive a member, you need to know the list ID and member email address beforehand. The archiving is simple and straight forward as shown below.

chimp := mailchimp.NewClient("key", "region")
if err := chimp.ArchiveMember("list-id", "[email protected]"); err != nil {
	return handleErr(err)
}

Fetching a members tags

It is possible to fetch all the tags associated with a given member for a given list. However, it is required that the lists ID and the members email address is known beforehand. To fetch the tags, simply use the FetchMemberTags receiver function on your mailchimp.Client. As example is given below. Please note that this function will only return an error is something went wrong on the MailChimp API side.

chimp := mailchimp.NewClient("key", "region")
tags, err := chimp.FetchMemberTags("list-id", "[email protected]")
if err != nil {
    return handleErr(err)
}

Adding/removing Tags

In order to add or remove tags for a given member of a given list, the members email address and the lists ID must be known beforehand. Before the operation can be performed, the client must first build a set of tags to either add or remove. This is simply done with TagBuilder as shown below.

tag, err := mailchimp.TagBuilder{}.
    Name("my-tag").
    StatusActive().
    Build()

The Build receiver function will return an error if the tags name has not been properly specified. There are also two possible statuses for a tag, active and inactive. Their corresponding builder receiver functions are listed below.

  • builder.StatusActive()
  • builder.StatusInactive()

Setting a tags status to inactive means that if the tag already exists on the member for the specified list at MailChimp, then that tag will be removed from the member. Setting the status as active simply means that the tag will be added to the member.

Once the tags has been created, you can send them to MailChimp like so:

chimp := mailchimp.NewClient("key", "region")
tags := createTags()
if err := chimp.UpdateMemberTags("list-id", "[email protected]", tags); err != nil {
    return handleErr(err)
}

There is also another version of UpdateMemberTags called UpdateMemberTagsSync. Using UpdateMemberTagsSync will make sure that any automations at MailChimp based on tags are not ran during the update. Please note that this also means that using UpdateMemberTags to update the tags will cause these automations to run, if any are set up. Please note that both of these receiver functions will only return an error if one occured on the MailChimp API side.

Batching addition/removal of tags

In order to add or remove several tags at once, or at least seemingly, you must use the clients BatchOperations method. This method takes a slice of Operation values, and to create such values for the addition/removal of tags you must use the NewTagsOperation function. An example is given below.

chimp := mailchimp.NewClient("key", "region")
tag1, _ := mailchimp.TagBuilder{}.Name("batched-tag-1").StatusActive()
tag2, _ := mailchimp.TagBuilder{}.Name("batched-tag-2").StatusActive()
tags := []mailchimp.Tag{tag1, tag2}

op1, _ := mailchimp.NewTagsOperation("list-id", "[email protected]", tags)
if err := chimp.BatchOperations([]mailchimp.Operation{op1}); err != nil {
	handleErr(err)
}

Webhooks

It is possible to add Webhooks unto your MailChimp audience using the mailchimp.Client. To create a new client, simply call mailchimp.NewClient with the API key and region for your MailChimp account. After creating a client you can do add, fetch and delete Webhooks on your MailChimp audience. Each of these operations are described with examples below.

Create Webhook

To add a Webhook you must know the URL to which events should be sent and the list ID of the audience you want to listen to. You can specify the types of events that should be sent to the Webhook using mailchimp.WebhookEvents, as well as the preferred event sources using mailchimp.WebhookSources. It is recommended to use mailchimp.WebhookBuilder{} when creating your Webhook as it will perform some basic validation on the data before it is sent off to MailChimp. A simple example of creating a Webhook is shown below. As can be seen, the mailchimp.Webhook only needs to be sent as a parameter to chimp.CreateWebhook in order to finalize the operation.

Note: Even though MailChimp uses HTTP POST requests to forward events to the Webhook, an initial HTTP GET request will be made to it at the point of creating the Webhook. Make sure to have a handler for a GET request that returns 200 OK as well as a POST handler for your Webhook.

wh, err := mailchimp.WebhookBuilder{}.
	URL("http://your-url.com/webhook").
	ListID("list-id").
	Events(mailchimp.WebhookEvents{
		Subscribe: true,
	}).
	Sources(mailchimp.WebhookSources{
		Admin: true,
	}).
	Build()
if err != nil {
	handleErr(err)
}

chimp := mailchimp.NewClient("list-id", "region")
wh, err = chimp.CreateWebhook(wh)
if err != nil {
	handleErr(err)
}

The full set of options for events and sources are shown below.

type WebhookEvents struct {
	Subscribe   bool
	Unsubscribe bool
	Profile     bool
	Cleaned     bool
	UpEmail     bool
	Campaign    bool
}

type WebhookSources struct {
	User  bool
	Admin bool
	API   bool
}

Fetch a Webhook by ID

It is rather straight forward to fetch a Webhook by its ID. Simply call FetchWebhook with the list ID and Webhook ID as parameters as shown below.

chimp := mailchimp.NewClient("key", "region")
webhook, err := chimp.FetchWebhook("list-id", "webhook-id")
if err != nil {
    handleErr(err)
}

Delete a Webhook

To delete a Webhook, all that is required is the list ID as well as the Webhook ID. Simply call DeleteWebhook and check for an error to complete the operation. An example is shown below.

chimp := mailchimp.NewClient("key", "region")
if err := chimp.DeleteWebhook("list-id", "webhook-id"); err != nil {
    handleErr(err)
}

Testing

Mocking the MailChimp provider

While running automated tests, it is very likely that you do not want go-mailchimp to send real requests to the MailChimp Marketing API. To avoid this, one can use the mailchimp.NewCustomDependencyClient to instantiate a client in place of the mailchimp.NewClient function. This function requires a value of the type mailchimp.MailChimpProviderMock to be sent in as a parameter. Using this mock, you can define the behaviour of the MailChimp endpoints for GET, PATCH, POST and DELETE calls. Thus, if you need to test how your software behaves when an error is returned from go-mailchimp you can simply define a function that returns an arbitrary error. By inspecting for example the PostCalls field on the mailchimp.MailChimpProviderMock you can also see how many POST requests were made during the test.

The mailchimp.MailChimpProviderMock struct is specified below.

type MailChimpProviderMock struct {
	PostMock    func(uri string, payload interface{}) ([]byte, error)
	PostCalls   int
	GetMock     func(uri string) ([]byte, error)
	GetCalls    int
	PatchMock   func(uri string, payload interface{}) ([]byte, error)
	PatchCalls  int
	DeleteMock  func(uri string) ([]byte, error)
	DeleteCalls int
}

An example of using the mock is shown below.

mock := mailchimp.MailChimpProviderMock{
	PostMock: func(s string, i interface{}) ([]byte, error) {
		return nil, errors.New("something went wrong")
	},
}
chimpMock := mailchimp.NewMockClient(&mock)

// perform some actions/operations here

if mock.PostCalls != 1 {
    t.Errorf("expected 1 PostCall, got %d", mock.PostCalls)
}

Mocking the entire client

Furthermore, to mock the entire mailchimp.Client value, you can use the mailchimp.ClientMock which satisfies the interface for a regular mailchimp.Client but it leaves the application developer to define its behaviour. Any of the receiver functions can be mocked, and all of them are paired with a counter that signifies the amount of times the function has been called thus far. The usage of mailchimp.ClientMock is very similar to that of mailchimp.MailChimpProviderMock and an example is showcased below.

mock := mailchimp.ClientMock{
    PingMock: func() error {
        return errors.New("could not connect to MailChimp")
    }
}

// inject the dependency and perform testing

if mock.PingCalls != 1 {
    t.Errorf("expected 1 call to Ping, but got %d", mock.PingCalls)
}

The mailchimp.ClientMock struct is shown below.

type ClientMock struct {
	PingMock  func() error
	PingCalls int

	CreateListMock  func(List) (List, error)
	CreateListCalls int
	FetchListsMock  func() ([]List, error)
	FetchListsCalls int
	FetchListMock   func(string) (List, error)
	FetchListCalls  int
	UpdateListMock  func(string, List) (List, error)
	UpdateListCalls int
	DeleteListMock  func(string) error
	DeleteListCalls int

	BatchMock            func(string, []Member) error
	BatchCalls           int
	BatchWithUpdateMock  func(string, []Member) error
	BatchWithUpdateCalls int

	FetchMemberTagsMock       func(string, string) ([]Tag, error)
	FetchMemberTagsCalls      int
	UpdateMemberTagsMock      func(string, string, []Tag) error
	UpdateMemberTagsCalls     int
	UpdateMemberTagsSyncMock  func(string, string, []Tag) error
	UpdateMemberTagsSyncCalls int

	ArchiveMemberMock  func(string, string) error
	ArchiveMemberCalls int

	CreateWebhookMock  func(webhook Webhook) (Webhook, error)
	CreateWebhookCalls int
	FetchWebhookMock   func(listID string, webhookID string) (Webhook, error)
	FetchWebhookCalls  int
	DeleteWebhookMock  func(listID string, webhookID string) error
	DeleteWebhookCalls int
}