NAV
Node curl

Introduction

API Endpoint
https://api.mailshake.com/2017-04-01

// Install our node package
npm install mailshake-node --save

// See source here
https://github.com/colinmathews/mailshake-node

Thank you for checking out the Mailshake API! We ♥️ devs because that’s who we are, so we hope you’ll find our API enjoyable. If you notice a typo or have a suggestion on making this documentation better, just open up a pull request on the repository that runs this site and we’ll check it out.

You’ll interact with the Mailshake API by making GET or POST requests (POST is recommended when sending larger payloads). All responses are JSON-formatted, and each application has its own quota limits based on your Mailshake subscription.

Getting your API key

To get your API key, go to the Extensions > API page in Mailshake and create your key. If your team is looking for higher limits, click the “Contact Us” on that page.

API Menu Item

Making requests

When making a POST request you can send content as a typical form payload by using the header:

Content-Type: application/x-www-form-urlencoded

or you can write JSON data to the request by using the header:

Content-Type: application/json

See authentication for examples of how to make requests and limits to understand your app’s constraints.

Responses

Single-item responses will be in this format:

{
  "object": "campaign",
  "id": 1,
  "title": "My campaign",
  "created": "2017-08-19T02:31:22.218Z"
}

Most endpoints return a single model of data. Check out our models section for specific examples and be sure to check out the errors section section, too.

Pagination

Paginated data will look like this:

{
  "nextToken": "...",
  "results": [
    { ... },
    { ... }
  ]
}

Get the next page of data like so:

mailshake.campaigns.list()
  .then(result => {
    console.log(`Page 1: ${JSON.stringify(result.results, null, 2)}`);
    // Just call `next` on the result to fetch the next page.
    return result.next();
  })
  .then(result => {
    console.log(`Page 2: ${JSON.stringify(result.results, null, 2)}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/list" \
  -u "my-api-key:" \
  -d nextToken=...

Endpoints that return multiple results will include a nextToken parameter that you can pass into another request to fetch the next results. These endpoints will also accept a perPage parameter to control the size of the record sets.

If nextToken is null then you’re looking at the last page of data.

Versioning

As we develop future versions of our API that are not backwards-compatible, we will leave the old version running and create a new url for the latest version. We will retain support for obsolete versions for a generous period of time and will send email notifications of any changes.

Current version:
https://api.mailshake.com/2017-04-01

Future version format:
https://api.mailshake.com/XXXX-YY-ZZ

Authentication

Most of the apps authorized to use the Mailshake API can only access their own team’s data. For these apps, you can use our simple authentication. Any app can use our OAuth version 2 authentication.

Simple

var mailshake = require('mailshake-node')('my-api-key');
mailshake.me()
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
# curl uses the -u flag to pass basic auth credentials
# (adding a colon after your API key prevents cURL from asking for a password).
curl "https://api.mailshake.com/2017-04-01/me" \
  -u "my-api-key:"

Simply include your API key as a querystring parameter (apiKey), part of your body json (apiKey), or via an http Authorization header that looks like:

Authorization: Basic [base-64 encoded version of your api key]

Make sure to replace my-api-key with your API key.

OAuth2

We’re currently in the process of reviewing whether to support OAuth2 for public use.

Test connection

mailshake.me()
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/me" \
  -u "my-api-key:"

This endpoint returns a simple object with a User model attached.

{
  "user": "[User model]"
}

You can hit our /me endpoint to test that authentication is working. It will return information about the current user you are authenticating as.

Campaigns

List

mailshake.campaigns.list({
  search: 'Venkman'
})
  .then(result => {
    result.results.forEach(campaign => {
      console.log(JSON.stringify(campaign, null, 2));
    });
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/list" \
  -u "my-api-key:" \
  -d search=Venkman

This endpoint returns paginated Campaign models.

List all of a team’s campaigns.

Parameters

Parameter Default Required Description
search No Filters what campaigns are returned.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Get

mailshake.campaigns.get({
  campaignID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/get" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns a Campaign model.

Retrieves a single campaign and its message sequence. A not_found error will be returned if the campaign could not be found.

Parameters

Parameter Default Required Description
search No Filters what campaigns are returned.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Pause

mailshake.campaigns.pause({
  campaignID: 1
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/pause" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns an empty response.

Immediately pauses all sending for a campaign. If a batch of emails for this campaign is currently being sent they will not be stopped.

Parameters

Parameter Default Required Description
campaignID Yes The campaign to pause.

Unpause

mailshake.campaigns.unpause({
  campaignID: 1
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/unpause" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns an empty response.

Resumes sending for a campaign. This team’s sending calendar will reschedule itself to account for this campaign’s pending emails. In rare cases it may take up to 5 minutes for the calendar to show scheduled times for this campaign.

Parameters

Parameter Default Required Description
campaignID Yes The campaign to unpause.

Recipients

Add

mailshake.recipients.add({
  campaignID: 1,
  addAsNewList: true,
  listOfEmails: '"John Doe" <john@doe.com>, "Jane Doe" <jane@doe.com>'
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/add" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d addAsNewList=true \
  -d listOfEmails="\"John Doe\" <john@doe.com>, \"Jane Doe\" <jane@doe.com>"

Adds new recipients to a campaign. Each campaign can hold up to 5,000 recipients.

If you pass along a full name for your recipients, Mailshake will automatically prepare first, last, and name (full name) as text replacements. If you only have a first name, use that value as their full name.

Any other fields you provide (like favorite_color) can be used as text replacements within your campaign. If a text replacement isn’t found, that recipient’s emails will not be scheduled until you make corrections.

Here’s an example message:

Hi {{first}},

Thanks for adding your name to my email list on {{topic}}. I think you’re really going to like it!

Parameters

Parameter Default Required Description
campaignID Yes The campaign to add these recipients to.
addAsNewList false No Pass true to keep these recipients grouped together. Otherwise they’ll be added to the last list you uploaded to your campaign.
listOfEmails Maybe A comma or newline separated list of email addresses to add. You can include recipient names by using the format: "John Doe <john@doe.com>"
addresses Maybe A structured list of recipient data that can include custom fields.
csvData Maybe A structured object representing a spreadsheet of comma-separated recipient data that can include custom columns as fields.

Using addresses

mailshake.recipients.add({
  campaignID: 1,
  addAsNewList: true,
  addresses: [
    {
      emailAddress: 'john@doe.com',
      fullName: 'John Doe',
      fields: {
        favorite_color: 'Red'
      }
    }
  ]
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/add" \
    -u "my-api-key:" \
    -H "Content-Type: application/json" \
    -X POST -d '{"campaignID":1,"addAsNewList":true,"addresses":[{"emailAddress":"john@doe.com","fullName":"John Doe","fields":{"favorite_color":"Red"}}]}'

This option lets you pass in an array of structured data. Each array item should be listed in this format;

Key Required Description
emailAddress Yes The recipient’s email address.
fullName No The recipients full name, or first name if that’s all you have.
fields No A simple JSON hash (not an array) of keys to values. Each field can be used as a text replacement.

Using csvData

mailshake.recipients.add({
  campaignID: 1,
  addAsNewList: true,
  csvData: {
    csvRawData: 'email,name,favorite_color\njohn@doe.com,John Doe,Red',
    emailColumnName: 'email',
    fullNameColumnName: 'name'
  }
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/add" \
  -u "my-api-key:" \
  -H "Content-Type: application/json" \
  -X POST -d '{"campaignID":1,"addAsNewList":true,"csvData":{"csvRawData":"email,name,favorite_color\njohn@doe.com,John Doe,Red","emailColumnName":"email","fullNameColumnName":"name"}}'

Pass in spreadsheet data in comma-separated-values format. You must have a column (of any name) that represents an email address, and optionally a column to represent a full name. All other columns will be translated into fields that can be used for text replacements.

Here is the format of the JSON object to be passed as the csvData parameter:

Key Required Description
csvRawData Maybe Raw csv-formatted data.
link Maybe A publicly accessible link that hosts csv-formatted data.
emailColumnName Yes The name of the column that represents recipients’ email addresses.
fullNameColumnName No The name of the column that should represent recipients’ full names (or first names if that’s all you have).

Response

This endpoint returns a AddedRecipients model.

Adding recipients is an asynchronous process and may take a few minutes to fully process, but you’ll receive an immediate response to indicate how things are going. Email addresses that are on your unsubscribe list or ones that are already in your campaign will be ignored.

AddStatus

mailshake.recipients.addStatus({
  statusID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/add-status" \
  -u "my-api-key:" \
  -d statusID=1

This endpoint returns a AddStatusResponse model.

Adding recipients is an asynchronous process, so this endpoint lets you check on how things are going. If isFinished is true, then the import has completed. The problems field will let you determine the exact success or failure of the import.

List

mailshake.recipients.list({
  campaignID: 1,
  search: 'Egon'
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/list" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d search=Egon

This endpoint returns paginated Recipient models.

Lists all of the recipients in a campaign. You can use this endpoint to search recipients, filter by activity, or find recipients who have some of kind of problem (like a missing text replacement or an email that failed to send).

Parameters

Parameter Default Required Description
campaignID Yes The campaign to look in.
filter No Criteria to filter recipients with.
search No Filters what recipients are returned.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Filter options

// Find recipients who have not opened any messages
mailshake.recipients.list({
  campaignID: 1,
  filter: {
    action: 'opened',
    negateAction: true
  }
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/list" \
  -u "my-api-key:" \
  -H "Content-Type: application/json" \
  -X POST -d '{"campaignID":"1","filter":{"action":"opened","negateAction":true}}'
Field Default Required Description
action Yes The kind of recipient activity to look for.
negateAction false No true to find recipients who have NOT taken the given action.
campaignMessageID No If action is based on a message, you can limit it to only look at this message.

Filter actions

Action Description
opened Recipients who have opened a message.
clicked Recipients who have clicked a link.
replied Recipients who have replied.
wasSent Recipients who were sent a message.
bounced Recipients who bounced.
paused Recipients who are paused.
hasProblems Recipients who have a missing text replacement or a failed sent email.

Get

mailshake.recipients.get({
  campaignID: 1,
  emailAddress: 'john@doe.com'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/get" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d emailAddress=john@doe.com

This endpoint returns a Recipient model.

Gets a single recipient’s basic information. A not_found error will be returned if the recipient could not be found.

Parameters

Parameter Default Required Description
recipientID Maybe The ID of a recipient.
campaignID Maybe The campaign that this recipient belongs to. Required if emailAddress is specified.
emailAddress Maybe The address of the recipient.

Pause

mailshake.recipients.pause({
  campaignID: 1,
  emailAddress: 'john@doe.com'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/pause" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d emailAddress=john@doe.com

This endpoint returns a Recipient model.

Immediately pauses all sending for a single recipient. If any emails for recipient are currently being sent they will not be stopped.

A not_found error will be returned if the recipient could not be found.

Parameters

Parameter Default Required Description
campaignID Yes The campaign that this recipient belongs to.
emailAddress Yes The address of the recipient.

Unpause

mailshake.recipients.unpause({
  campaignID: 1,
  emailAddress: 'john@doe.com'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/unpause" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d emailAddress=john@doe.com

This endpoint returns a Recipient model.

Resumes sending for a recipient. This team’s sending calendar will reschedule itself to account for this recipient’s pending emails. In rare cases it may take up to 5 minutes for the calendar to show updated scheduled times.

A not_found error will be returned if the recipient could not be found.

Parameters

Parameter Default Required Description
campaignID Yes The campaign to unpause.
emailAddress Yes The address of the recipient.

Unsubscribe

mailshake.recipients.unsubscribe({
  emailAddresses: 'john@doe.com,jane@doe.com'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/recipients/unsubscribe" \
  -u "my-api-key:" \
  -d emailAddresses=john@doe.com,jane@doe.com

This endpoint returns an empty response.

Adds a list of email addresses to your unsubscribe list.

Parameters

Parameter Default Required Description
emailAddresses Yes A comma-separated list of email addresses to unsubscribe.

Activity

These endpoints let you see what’s been going on with your campaigns.

Sent

mailshake.activity.sent({
  campaignMessageType: 'initial'
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/sent" \
  -u "my-api-key:" \
  -d campaignMessageType=initial

This endpoint returns paginated SentMessage models.

Obtains the most recent emails you have sent. In most cases you’ll want to look at campaign-based emails, but this endpoint also lets you get one-off replies you’ve sent within Mailshake via Lead Catcher.

Parameters

Parameter Default Required Description
messageType any No If specified, you can filter to only one-off or campaign messages. one-off messages are replies you send manually within Mailshake, so in most cases you’ll want to omit this parameter or use campaign.
campaignMessageType any No Filter to a specific type of message within a campaign (see CampaignMessageTypes).
campaignID No Restrict to a single campaign.
campaignMessageID No Restrict to a single message within a campaign.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Opens

mailshake.activity.opens()
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/opens" \
  -u "my-api-key:"

This endpoint returns paginated Open models.

Obtains the most recent emails opened.

Parameters

Parameter Default Required Description
campaignID No Restrict to a single campaign.
campaignMessageID No Restrict to a single message within a campaign.
excludeDuplicates false No If true this will only not return data when recipients open the same email more than once.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Clicks

mailshake.activity.clicks()
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/clicks" \
  -u "my-api-key:"

This endpoint returns paginated Click models.

Obtains the most recent links clicked.

Parameters

Parameter Default Required Description
campaignID No Restrict to a single campaign.
excludeDuplicates false No If true this will only not return data when recipients click the same link more than once.
matchUrl No An exact matching of a specific link you’re tracking.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Replies

mailshake.activity.replies({
  replyType: 'reply'
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/replies" \
  -u "my-api-key:" \
  -d replyType=reply

This endpoint returns paginated Reply models.

Obtains the most recent replies to your sent emails. Pay special attention to replyType because you can use this endpoint to look at bounces, out-of-office replies, etc.

Parameters

Parameter Default Required Description
replyType any No The type of replies to filter to.
campaignID No Restrict to a single campaign.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Created Leads

mailshake.activity.createdLeads({
  campaignID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/created-leads" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns paginated Lead models.

Obtains the most recently created leads. Usually leads are automatically created from the rules you’ve set up in Lead Catcher, but Mailshake users can also manually turn recipients into leads.

Parameters

Parameter Default Required Description
campaignID No Restrict to a single campaign.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
assignedToEmailAddress No Only get leads that are assigned to this person on your team.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Lead Status Changes

mailshake.activity.leadStatusChanges({
  campaignID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/activity/lead-status-changes" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns paginated Lead models.

Obtains the most recently updated leads. A lead can be closed, ignored, opened, or reopened. A reopened lead has open as its status, it’s just that at one point that lead had been ignored or closed.

Parameters

Parameter Default Required Description
campaignID No Restrict to a single campaign.
recipientEmailAddress No Limit to specific recipients. If the value passed is not in the format of an email address, a fuzzy search will be done. Ergo, sending in firmxyz.com will match anyone on that domain.
assignedToEmailAddress No Only get leads that are assigned to this person on your team.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Leads

A lead in Mailshake is a recipient who may be interested in whatever you’re pitching in your campaigns. Lead Catcher will automatically find leads based on criteria you set up, but you can also create and manage leads via the API.

List

mailshake.leads.list({
  campaignID: 1,
  status: 'open'
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/list" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d status=open

This endpoint returns paginated Lead models.

Lists your leads. You can use this endpoint to search leads, filter by status, or find leads assigned to one of your teammates.

Parameters

Parameter Default Required Description
campaignID No Filter leads to the ones from this campaign.
status No Filter to leads in a particular status.
assignedToEmailAddress No Leads assigned to this teammate.
search No Filters what leads are returned.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Get

mailshake.leads.get({
  campaignID: 1,
  emailAddress: 'john@doe.com'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/get" \
  -u "my-api-key:" \
  -d campaignID=1 \
  -d emailAddress=john@doe.com

This endpoint returns a Lead model.

Gets a single lead. A not_found error will be returned if the lead could not be found.

Parameters

Parameter Default Required Description
leadID Maybe The ID of a lead.
recipientID Maybe The ID of the recipient that this lead is for.
campaignID Maybe The campaign that this recipient belongs to. Required if emailAddress is specified.
emailAddress Maybe The address of the recipient.

Create

mailshake.leads.create({
  recipientIDs: [1, 2, 3]
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });

// Or

mailshake.leads.create({
  campaignID: 1,
  emailAddresses: [
    'a@johndoe.com',
    'c@johndoe.com',
    'd@johndoe.com'
  ]
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/create" \
  -u "my-api-key:" \
  -d recipientIDs=1
  -d recipientIDs=2
  -d recipientIDs=3

# Or

curl "https://api.mailshake.com/2017-04-01/leads/create" \
  -u "my-api-key:" \
  -d campaignID=1
  -d emailAddresses=a@johndoe.com
  -d emailAddresses=b@johndoe.com
  -d emailAddresses=c@johndoe.com

This endpoint returns CreatedLeads model.

Creates one or more leads from recipients of a campaign. You can either pass in the IDs of recipients or if it’s easier, you can pass their email addresses instead. If a recipient was already a lead and was closed, this will reopen them as a lead.

Parameters

You can specify recipientIDs or emailAddresses or both.

Parameter Default Required Description
campaignID No The ID of the campaign from which to create a lead.
emailAddresses No A list of email addresses to find recipients from for creating leads. This list will be added to the recipientIDs parameter if both are passed.
recipientIDs No A list of recipient IDs to create leads from. This list will be added to the recipientIDs parameter if both are passed.

Close

mailshake.leads.close({
  leadID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/close" \
  -u "my-api-key:" \
  -d leadID=1

This endpoint returns an empty response.

Marks a lead as “closed” which means you completed a successful interaction. The opposite of closing a lead is ignoring a lead.

Parameters

Other than campaignID, only one identifier is required – just use what’s most convenient to you.

Parameter Default Required Description
campaignID No
emailAddress No The email address of a recipient in this campaign.
recipientID No The ID of the recipient that this lead refers to.
leadID No The ID of the lead.

Ignore

mailshake.leads.ignore({
  leadID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/ignore" \
  -u "my-api-key:" \
  -d leadID=1

This endpoint returns a LeadStatus model.

Marks a lead as “ignored” when means the conversation didn’t go anywhere. This is the opposite of “closing” a lead.

Parameters

Other than campaignID, only one identifier is required – just use what’s most convenient to you.

Parameter Default Required Description
campaignID No
emailAddress No The email address of a recipient in this campaign.
recipientID No The ID of the recipient that this lead refers to.
leadID No The ID of the lead.

Reopen

mailshake.leads.reopen({
  leadID: 1
})
  .then(result => {
    console.log(JSON.stringify(result, null, 2));
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/leads/reopen" \
  -u "my-api-key:" \
  -d leadID=1

This endpoint returns a LeadStatus model.

Takes a closed or ignored lead and makes it open again and available for review.

Parameters

Other than campaignID, only one identifier is required – just use what’s most convenient to you.

Parameter Default Required Description
campaignID No
emailAddress No The email address of a recipient in this campaign.
recipientID No The ID of the recipient that this lead refers to.
leadID No The ID of the lead.

Team

List Members

mailshake.team.listMembers()
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/team/list-members" \
  -u "my-api-key:"

This endpoint returns paginated User models.

Lists the users belonging to this team.

Parameters

Parameter Default Required Description
search No Filters the returned users.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.

Pushes

The type of request we’ll send to your servers:

{
  "resource_url": "https://api.mailshake.com/2017-04-01/..."
}
let express = require('express');
let bodyParser = require('body-parser');
let PushHandler = require('mailshake-node').PushHandler;

// Start your web server
let app = express();
app.use(bodyParser.json({}));

// Hook up the Mailshake push handler
let handler = new PushHandler(mailshake, {
  baseUrl: 'https://yourwebsite.com',
  rootPath: 'path/to/your/handler',
  secret: 'my-secret'
});
handler.on('push', push => {
  console.log(JSON.stringify(push, null, 2));
});
handler.on('pushError', err => {
  console.error(`${err.code}: ${err.stack}`);
});
handler.hookExpress(app);

// Start your server
app.listen(80);

// Verify that it's hooked up properly by visiting your site, something like:
// https://yourwebsite.com/path/to/your/handler/my-secret/some-unique-id

One of the cooler things you can do with the Mailshake API is subscribe to pushes. Whenever an action occurs that you’ve subscribed to, Mailshake will make a HTTP request to your servers so you can react in real time.

Your servers need to accept POST requests from us using the application/json content type. We’ll include a simple object with resource_url as its only key. Send an HTTP request to this url and be sure to include the same authentication as with our other API operations.

Your server should respond to us with a 200 status indicating that you’ve handled the request (otherwise we’ll wait and try a few more times in case your server is under maintenance).

Create

handler.subscribe('Clicked')
  .then(targetUrl => {
    // Save your targetUrl somewhere so you can unsubscribe later
  })
  .catch(err => {
    console.error(`${err.code}: ${err.stack}`);
  });
curl "https://api.mailshake.com/2017-04-01/push/create" \
  -u "my-api-key:" \
  -d event=Clicked \
  -d targetUrl=https://mysite.com/some-secret/some-unique-id

Starts a subscription for a type of push.

Parameters

Parameter Default Required Description
targetUrl Yes The publicly accessible url to your servers that we should send a POST request to. We highly recommend using https and including a secret key so that other actors can’t send requests as if they were Mailshake.
event Yes The type of event you’re subscribing to.
filter No If you only want subscriptions when certain criteria are met, specify them as a JSON object.

Filters

You can apply a filter to some events so that you only get pushes that meet certain criteria.

Filter fields:

Field Description
campaignID To only get events for a specific campaign.
campaignMessageID For events based on message, only get events for a specific message.
excludeDuplicates true if you don’t want to get pushes for duplicate opens or clicks.
matchUrl For clicks you can only be notified when this exact url is clicked.
messageType For sent messages you can be notified only when certain types of messages are sent.

Events

Event Description
Clicked Someone clicked a link.
Opened Someone opened an email.
Replied Someone replied to one of your emails.
MessageSent Mailshake sent an email on your behalf.
LeadCreated A lead was created.
LeadStatusChanged A lead’s status was changed.

Delete

let targetUrl; // Look this up from when you subscribed
handler.unsubscribe(targetUrl)
  .then(() => {
    // Done
  })
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/push/delete" \
  -u "my-api-key:" \
  -d targetUrl=https://mysite.com/some-secret/some-unique-id

Unsubscribes a push you previously created. Since all subscribed pushes require a unique targetUrl, that’s all you need to send in to delete your push.

Parameters

Parameter Default Required Description
targetUrl Yes The unique target url of a push.

Models

Most models will have these common fields:

Field Type Description
object string The type of model this data represents.
id integer The unique ID of this data.

User

{
  "object": "user",
  "id": 1,
  "teamID": 1,
  "teamName": "My Team",
  "isTeamAdmin": true,
  "isDisabled": false,
  "emailAddress": "me@example.com",
  "fullName": "Jane Doe",
  "first": "Jane",
  "last": "Doe",
  "teamBlockedDate": null
}

A Mailshake user and the team that they are on.

Campaign

{
  "object": "campaign",
  "id": 1,
  "title": "My campaign",
  "created": "2017-08-19T02:31:22.218Z",
  "messages": [
    {
      "object": "message",
      "id": 1,
      "type": "initial",
      "subject": "My subject"
    },
    {
      "object": "message",
      "id": 2,
      "type": "follow-up",
      "subject": "null"
    },
    {
      "object": "message",
      "id": 3,
      "type": "follow-up",
      "subject": "null"
    },
    {
      "object": "message",
      "id": 4,
      "type": "drip",
      "subject": "Here's a drip email"
    },
    {
      "object": "message",
      "id": 5,
      "type": "on-click",
      "subject": "You clicked my link"
    }
  ]
}

A Mailshake campaign is the container for a sequence of messages and the recipients to whom they’ll be sent.

MessageTypes

Type Description
initial The first email in a campaign.
follow-up
Replies to the initial message sent only if the recipient doesn’t reply.
drip Secondary emails that are not stopped by a recipient’s reply.
on-click
Email fired whenever the recipient clicks a specific link in any of the emails.

CreatedLeads

{
  "leads": [
    {
      "recipientID": 1,
      "leadID": 1  
    }
  ],
  "emailsNotFound": [
    "me@example.com"
  ],
  "recipientIDsNotFound": [2, 3]
}

The results of an attempt to create leads.

LeadStatus

{
  "status": "ignored",
  "leadID": 1
}

The result of a status change of a lead. See Lead Statuses.

SentMessage

{
  "object": "sent-message",
  "id": 1,
  "actionDate": "2017-04-06T17:45:07.188Z",
  "recipient": {
    "object": "recipient",
    "id": 399,
    "emailAddress": "john@doe.com",
    "fullName": "John Doe",
    "first": "John",
    "last": "Doe",
    "created": "2017-04-06T17:45:07.188Z",
    "fields": {
      "favorite_color": "Red"
    }
  },
  "campaign": {
    "object": "campaign",
    "id": 248,
    "title": "My Campaign",
    "created": "2017-04-06T17:45:07.188Z"
  },
  "type": "campaign-message",
  "message": {
    "object": "message",
    "id": 423,
    "type": "initial",
    "subject": "My subject"
  },
  "from": {
    "object": "email-address",
    "address": "sally@doe.com",
    "fullName": "Sally Doe",
    "first": "Sally",
    "last": "Doe"
  },
  "to": [
    {
      "object": "email-address",
      "address": "john@doe.com",
      "fullName": "John Doe",
      "first": "John",
      "last": "Doe"
    }
  ],
  "subject": "This is my subject",
  "externalID": "...",
  "externalRawMessageID": "...",
  "externalConversationID": "...",
  "rawBody": "...",
  "body": "...",
  "plainTextBody": "..."
}

An email that was sent via Mailshake. Most messages have a messageType of campaign which means they were sent as part of a campaign sequence. Messages with one-off were manual replies sent via Lead Catcher.

Notable fields:

Field Description
message For campaign sent messages, this is the message inside your campaign’s sequence. one-off sent messages will omit this field.
externalID The ID of the email on the mail account’s platform.
externalRawMessageID The actual ID of the email message from the emails’ headers.
externalConversationID The ID of the email thread on the mail account’s platform.
rawBody The raw HTML.
body This is is the HTML with tracking and replies stripped out.
plainTextBody A display-friendly version of the email body.

Message Types

Type Description
one-off An email sent as a manual reply via Lead Catcher.
campaign An email sent as part of a campaign sequence.

Open

{
  "object": "open",
  "id": 1,
  "actionDate": "2017-04-06T17:40:23.194Z",
  "isDuplicate": false,
  "recipient": {
    "object": "recipient",
    "id": 1,
    "emailAddress": "john@doe.com",
    "fullName": "John Doe",
    "first": "John",
    "last": "Doe",
    "created": "2017-04-06T17:45:07.188Z",
    "fields": {
      "favorite_color": "Red"
    }
  },
  "campaign": {
    "object": "campaign",
    "id": 1,
    "title": "My Campaign",
    "created": "2017-04-06T17:40:23.194Z"
  },
  "parent": {
    "object": "sent-message",
    "id": 1,
    "type": "campaign-message",
    "message": {
      "object": "message",
      "id": 1,
      "type": "initial",
      "subject": "My subject"
    }
  }
}

The result of a recipient opening one of your sent emails.

Notable fields:

Field Description
parent An abbreviated version of the sent message that was opened. In most cases this is an email in your campaign sequence, but it could be a one-off email sent via Lead Catcher.
isDuplicate true if this recipient is opening the email for the second or nth time.

Click

{
  "object": "click",
  "id": 1,
  "link": "http://google.com",
  "actionDate": "2017-04-06T17:38:53.369Z",
  "isDuplicate": false,
  "recipient": {
    "object": "recipient",
    "id": 1,
    "emailAddress": "john@doe.com",
    "fullName": "John Doe",
    "first": "John",
    "last": "Doe",
    "created": "2017-04-06T17:45:07.188Z",
    "fields": {
      "favorite_color": "Red"
    }
  },
  "campaign": {
    "object": "campaign",
    "id": 1,
    "title": "My Campaign",
    "created": "2017-04-06T17:40:23.194Z"
  },
  "parent": {
    "object": "sent-message",
    "id": 1,
    "type": "campaign-message",
    "message": {
      "object": "message",
      "id": 1,
      "type": "initial",
      "subject": "My subject"
    }
  }
}

The result of a recipient clicking a link in one of your sent emails.

Notable fields:

Field Description
link The full url that was clicked.
parent An abbreviated version of the sent message that was opened. In most cases this is an email in your campaign sequence, but it could be a one-off email sent via Lead Catcher.
isDuplicate true if this recipient is clicking the link for the second or nth time.

Reply

{
  "object": "reply",
  "id": 1,
  "actionDate": "2017-04-06T17:42:54.475Z",
  "recipient": {
    "object": "recipient",
    "id": 1,
    "emailAddress": "john@doe.com",
    "fullName": "John Doe",
    "first": "John",
    "last": "Doe",
    "created": "2017-04-06T17:45:07.188Z",
    "fields": {
      "favorite_color": "Red"
    }
  },
  "campaign": {
    "object": "campaign",
    "id": 1,
    "title": "My Campaign",
    "created": "2017-04-06T17:40:23.194Z"
  },
  "type": "out-of-office",
  "parent": {
    "object": "sent-message",
    "id": 1,
    "type": "campaign-message",
    "message": {
      "object": "message",
      "id": 1,
      "type": "initial",
      "subject": "My subject"
    }
  },
  "subject": "Re: This is my subject",
  "externalID": "...",
  "externalRawMessageID": "...",
  "externalConversationID": "...",
  "rawBody": "...",
  "body": "...",
  "plainTextBody": "..."
}

Represents any kind of reply received from a recipient to one of your sent emails.

Notable fields:

Field Description
type The type of reply.
parent An abbreviated version of the sent message that was opened. In most cases this is an email in your campaign sequence, but it could be a one-off email sent via Lead Catcher.
externalID The ID of the email on the mail account’s platform.
externalRawMessageID The actual ID of the email message from the emails’ headers.
externalConversationID The ID of the email thread on the mail account’s platform.
rawBody The raw HTML.
body This is is the HTML with tracking and replies stripped out.
plainTextBody A display-friendly version of the email body.

ReplyType

Type Description
reply A normal reply.
bounce Information about why your email bounced.
out-of-office An “I’m out of the office” reply. Follow-ups will still be sent to these folks unless you pause them.
unsubscribe The user was unsubscribed because their reply requested us to do so.
delay-notification Your mail account indicated that your original message is still trying to be sent.

Lead

{
  "object": "lead",
  "id": 1,
  "created": "2017-04-11T16:13:51.956Z",
  "openedDate": "2017-04-11T16:13:51.955Z",
  "lastStatusChangeDate": null,
  "recipient": {
    "object": "recipient",
    "id": 1,
    "emailAddress": "john@doe.com",
    "fullName": "John Doe",
    "first": "John",
    "last": "Doe",
    "created": "2017-04-06T17:45:07.188Z",
    "fields": {
      "favorite_color": "Red"
    }
  },
  "campaign": {
    "object": "campaign",
    "id": 1,
    "title": "My Campaign",
    "created": "2017-04-11T16:13:51.956Z",
  },
  "status": "open",
  "assignedTo": {
    "object": "user",
    "id": 1,
    "emailAddress": "lucy@yourteam.com",
    "fullName": "Lucy Doe",
    "first": "Lucy",
    "last": "Doe"
  }
}

A recipient that was (at least at one time) a prospect. Leads are created in the open status and can be set closed or ignored.

Notable fields:

Field Description
status The current status of this lead.

Lead Statuses

Status Description
open The lead is available for review.
ignored The lead was classified as not going anywhere.
closed The lead was considered a successful interaction.

Added Recipients

{
  "invalidEmails": ["one@example.com", "two@example.com"],
  "isEmpty": false,
  "checkStatusID": 1
}

Notable fields:

Key Description
invalidEmails A list of email addresses that were not imported because they did not pass validation.
isEmpty true if no email addresses were actually imported.
checkStatusID An ID you can use to monitor the import.

Add Status Response

{
  "isFinished": true,
  "problems": {
    "unsubscribedEmails": ["one@example.com"],
    "alreadyInCampaignEmails": ["two@example.com"],
    "passedAccountLimitEmails": ["three@example.com"],
    "hasProblems": true
  }
}

Describes the progress in adding a batch of recipients.

Notable fields for problems:

Key Description
unsubscribedEmails A list of email addresses that are on your unsubscribe list.
alreadyInCampaignEmails A list of email addresses that were already part of this campaign.
passedAccountLimitEmails A list of email addresses that could not be imported because your campaign exceeded the number of allowed recipients it can hold.

Recipient

{
  "object": "recipient",
  "id": 1,
  "emailAddress": "john@doe.com",
  "fullName": "John Doe",
  "first": "John",
  "last": "Doe",
  "created": "2017-04-06T17:45:07.188Z",
  "fields": {
    "favorite_color": "Red"
  }
}

Notable fields:

Key Description
fields A simple JSON hash (not an array) of keys to values. Each field can be used as a text replacement.

Errors

A raw error response looks like this:

{
  "code": "invalid_api_key",
  "error": "Invalid api key",
  "time": "2017-08-21T13:23:11.814Z"
}

Mailshake uses the following error codes:

General errors

Code Description
invalid_api_key The key you provided us was either missing or invalid.
missing_team_admin Your team doesn’t have an active team administrator (check that your billing is up to date)
missing_dependent_data The object on which you’re acting wasn’t found or you don’t have permission. An example would be trying to pause a campaign with an ID that doesn’t reference a campaign.
missing_parameter Your request is missing a required parameter.
invalid_parameter One of your request’s parameter is in an unsupported format.
not_authorized Your credentials don’t allow you to execute this request.
not_found A more semantically permissive version of missing_dependent_data. We’re splitting hairs a bit here, but generally speaking when this code is returned your application might just want to note the problem and carry on. Whereas if you encounter the missing_dependent_data error it’s more of a show-stopper.
exceeds_monthly_recipients You can’t add this many recipients because you will pass your monthly quota allowance. You can contact us to request an increase.
user_not_admin The user on file for your application must be an administrator of your team.
user_is_disabled The user on file for your application must have an active account (check your billing)
missing_subscription Your team must have an active and paid subscription to Mailshake.
team_blocked Your team has been blocked while our compliance team runs a review of your usage of our platform.
limit_reached Your application has exceeded it’s quota and must wait to make more requests, or you can contact us to request an increased quota. The message will indicate the next time you can try a request: Please wait and try again after: 2017-08-21T15:16:15.207Z
internal_error Something general went wrong with Mailshake. This may be a temporary issue or we may have a bug (if so notify us).
unspecified_error Your request was probably rejected by some kind of validation.

Specific to OAuth2

Code Description
unauthorized_request Your request is missing authentication.
invalid_client Your client_id or client_secret parameter is wrong.
invalid_grant Your request for authorization is malformed.
app_not_approved Your application is not allowed to be used by other teams.
invalid_token Your token has expired. Try using your refresh token to get another access token.
missing_scope Your authentication request did not specify any scopes.

Limits

Based on your plan with us, you app will have a few limitations on your usage. If you’re interested in increasing your limits, use the “Contact Us” button on the Extensions > API page inside Mailshake.

New recipients / month

Each Mailshake campaign can hold up to 5,000 recipients. However, adding recipients through the Mailshake API is limited to a certain number per month. This isn’t a per-campaign limit, it’s accumulative across all campaigns. If your app attempts to exceed this limit, you’ll get the exceeds_monthly_recipients error back.

The monthly window is aligned with the calendar year. Ergo on the 1st of each month your recipient limit will reset.

Rate limits

As an example of quota units work, check this out:

// Quota units: 400
mailshake.campaigns.pause(1)
// Quota units: 395
mailshake.campaigns.pause(2)
mailshake.campaigns.pause(3)
// Quota units: 385
mailshake.recipients.add([ /* 32 recipients */ ])
// This ^^ costs 20 + 32, or 52
// Quota units: 343
// ...an hour passes by ...
// Quota units: 400

Each call to our API costs a varying number of “quota units,” and you’re allowed X quota units per hour to limit how frequently you can make requests. If you hit a limit, you’ll get the error code limit_reached. The error message will indicate when you can try again (see our error codes).

Units Operation
10 Campaigns > List
5 Campaigns > Pause
25 Campaigns > Unpause
25 Leads > Create
5 Leads > Close
5 Leads > Ignore
5 Leads > Reopen
20 + N
Recipients > Add
10 Recipients > List
5 Recipients > Pause
10 Recipients > Unpause
2 Recipients > Unsubscribe
100 Push > Create
10 Activity > Clicks
10 Activity > Opens
10 Activity > Sent
10 Activity > Replies
10 Activity > CreatedLeads
10 Activity > LeadStatusChanges