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/mailshake/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, feel free to contact us.

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, you can contact us to request an increase.

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

// mailshake-node has hooks to support most any OAuth library.
// You can either customize the request with `customizeRequest`
// or outright replace how the request is made with `overrideCreateRequest`

var mailshake = require('mailshake-node')({
  customizeRequest(options) => {
    // options.headers.authorization = [...oauth header...]
    return options;
  }),

  // or

  overrideCreateRequest(options, callbackFn) => {
    // Create a standard node request via an OAuth tool
    // Something that looks like this:
    return https(options, callbackFn);
  })
});

If your app has been approved as a 3rd-party app (one in which you can access other users’ data instead of limited of just your own team), you must use OAuth V2 to integrate with us. We’ll deliver your consumer key and secret to you manually. The other applicable OAuth settings are below:

Authorization

Authorization URL: https://app.mailshake.com/oauth/

Request Parameters

Include the following parameters as part of the query string of the authorization URL:

Parameter Description
response_type=code Tells us that you are expecting to receive an authorization code
client_id The client ID for your application
redirect_uri The URI to send the user to after authorization is complete, must match a URI listed for your application
scope One or more scope values (see below) indicating which parts of the user’s account you need access to
state A random string generated by your application, which will be passed to the redirect_ui

OAuth Scope

Specify the OAuth scope to tell customers what permissions you’re requesting. Specify the scopes in a comma-delimited string like so: campaign-read,campaign-write

Scope Description
campaign-read Grants read access to all operations.
campaign-write Grants write access to all operations.

Response Parameters

Upon successful authorization, the user will be redirected to your redirect_uri with the following query string parameters:

Parameter Description
code The authorization code
state The same state value you passed to the authorization URL

Getting an Access Token

Access token URL: https://api.mailshake.com/2017-04-01/token

Request Parameters

POST the following parameters to the access token URL:

Parameter Description
grant_type=authorization_code Tells us that you are expecting to receive an authorization code
client_id The client ID for your application
client_secret The client secret for your application
code The code you obtained from the authorization URL
redirect_uri The same redirect URI passed to the authorization URL

Response Properties

The response from the access token URL will be in JSON with the following properties:

Property Description
access_token The token that can be used to be future requests
refresh_token The token that can be used to obtain a new access token

Refreshing an Access Token

Refresh token URL: https://api.mailshake.com/2017-04-01/token

Request Parameters

POST the following parameters to the refresh token URL:

Parameter Description
grant_type=refresh_token Tells us that you are refreshing a token
client_id The client ID for your application
client_secret The client secret for your application
refresh_token The refresh token you obtained from the access token URL
redirect_uri The same redirect URI passed to the authorization URL

Response Properties

The response from the access token URL will be in JSON with the following properties:

Property Description
access_token The new token that can be used to be future requests

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
campaignID Yes The ID of the campaign.

Create

mailshake.campaigns.create({
  title: 'My campaign'
})
  .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/create" \
  -u "my-api-key:" \
  -d title="My campaign"

This endpoint returns a Campaign model.

Creates a new campaign. This campaign cannot be sent until the user finishes the wizard in Mailshake’s user interface, but you can add recipients.

Parameters

Parameter Default Required Description
title
[Month] [Day] Outreach
No A title to give to the campaign.
senderID No The id of the sender to use for this campaign.

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.

Export

mailshake.campaigns.export({
  campaignIDs: [1, 2, 3],
  exportType: 'simple',
  timezone: 'America/Indianapolis'
})
  .catch(err => {
    console.error(`${err.code}: ${err.message}`);
  });
curl "https://api.mailshake.com/2017-04-01/campaigns/export" \
  -u "my-api-key:" \
  -H "Content-Type: application/json" \
  -X POST -d '{"campaignIDs":[1, 2, 3], "exportType": "simple", "timezone": "America/Indianapolis"}'

This endpoint returns a CampaignExportRequest model.

Asynchronously starts an export of one or more campaigns to CSV format. All campaign data will be included in a single csv file you can download.

Parameters

Parameter Default Required Description
campaignIDs Yes* An array of campaign IDs to export.
exportType Yes The type of export to perform (see below).
timezone UTC No The timezone that dates in the export should be based in.

Export types

Type Description
simple Recipient-based export, listing each recipient (per campaign) on a single row.
show-each-message
Export based on sent messages, listing every sent email and related stats on a single row.
unsubscribes An export of all the unsubscribed email addresses for your team.

ExportStatus

mailshake.campaigns.exportStatus({
  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/campaigns/export-status" \
  -u "my-api-key:" \
  -d statusID=1

This endpoint returns a CampaignExport model.

Exporting campaigns is an asynchronous process, so this endpoint lets you check on how things are going. If isFinished is true, then the export has completed. The csvDownloadUrl field provides the csv file you can download.

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.
truncateExtraFields false No Mailshake limits you to 30 recipient fields. A validation error will reject your request if you go over that limit unless you pass “true” for this parameter. In that case, we will simply ignore the extra fields.
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","twitterID":"jdoe","instagramID":"jdoe","facebookUrl":"https://facebook.com/jdoe","linkedInUrl":"https://linkedin.com/in/jdoe","account":"J. Doe and Co.","phoneNumber":"5555555555","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 recipient’s full name, or first name if that’s all you have.
twitterID No The recipient’s Twitter ID (just tne username, not the full URL).
instagramID No The recipient’s Instagram ID (just tne username, not the full URL).
facebookUrl No The recipient’s Facebook profile URL.
linkedInUrl No The recipient’s LinkedIn profile URL.
account No The account the recipient is associated with.
phoneNumber No The recipient’s phone number
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,phone,fb,linkedin,ig,twitter,accountName,favorite_color\naug25-b@mailinator.com,John Doe,1231232233,https://www.facebook.com/jdoe,https://www.linkedin.com/jdoe,jdoeinsta,jdoetwitter,J. Doe and Co.,Red","emailColumnName":"email","fullNameColumnName":"name","phoneNumberColumnName":"phone","facebookUrlColumnName":"fb","linkedInUrlColumnName":"linkedin","instagramIDColumnName":"ig","twitterIDColumnName":"twitter","accountColumnName":"accountName"}}'

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 contains recipient email addresses.
fullNameColumnName No The name of the column that contains recipient full names (or first names if that’s all you have).
phoneNumberColumnName No The name of the column that contains recipient phone numbers
facebookUrlColumnName No The name of the column that contains recipient Facebook profile URLs.
linkedInUrlColumnName No The name of the column that contains recipient LinkedIn profile URLs.
instagramIDColumnName No The name of the column that contains recipient Instagram IDs (just the username, not the full URL).
twitterIDColumnName No The name of the column that contains the recipient Twitter IDs (just the username, not the full URL)
accountColumnName No The name of the column that contains the recipient account names

Response

This endpoint returns a AddRecipientsRequest 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 AddedRecipients 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-message 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-message.
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.
excludeBody No Excludes the email body from the response.
nextToken No Fetches the next page from a previous request.
perPage 25 No How many results to get at once, up to 25.

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.
since No Restrict results to events that occurred after since. Date is in UTC, and formatted like “2017-04-11 16:13:51”
assignedToUserID No Restrict results to campaigns assigned to user

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.
since No Restrict results to events that occurred after since. Date is in UTC, and formatted like “2017-04-11 16:13:51”
assignedToUserID No Restrict results to campaigns assigned to user

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 Filter to only reply, bounce, out-of-office, unsubscribe or delay-notification replies.
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 25 No How many results to get at once, up to 25.

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.
since No Restrict results to events that occurred after since. Date is in UTC, and formatted like “2017-04-11 16:13:51”
assignedToUserID No Restrict results to campaigns assigned to user

Assigned Leads

mailshake.activity.leadAssignments({
  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-assignments" \
  -u "my-api-key:" \
  -d campaignID=1

This endpoint returns paginated Lead models.

Obtains the most recently assigned leads. Leads are assigned by Mailshake users manually assigning them.

Parameters

Parameter Default Required Description
campaignID No Restrict to a single campaign.
nextToken No Fetches the next page from a previous request.
perPage 100 No How many results to get at once, up to 100.
since No Restrict results to events that occurred after since. Date is in UTC, and formatted like “2017-04-11 16:13:51”
assignedToUserID No Restrict results to campaigns assigned to user

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.
since No Restrict results to events that occurred after since. Date is in UTC, and formatted like “2017-04-11 16:13:51”
assignedToUserID No Restrict results to campaigns assigned to user

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 won, 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 “Won” (or “Lost” if you send in that status). The alternative to these states is “Ignored” which means that the lead wasn’t worth pursuing.

Parameters

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.
status “closed” No Can be set to “closed” or “lost”.

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 lead wasn’t worth pursuing. Use Close and pass lost as the status to indicate you worked on this lead but it didn’t pan out.

Parameters

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

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.

Senders

List

mailshake.senders.list({
  search: '@gmail.com'
})
  .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/senders/list" \
  -u "my-api-key:" \
  -d search="@gmail.com"

This endpoint returns paginated Sender models.

List all of a team’s senders.

Parameters

Parameter Default Required Description
search No Filters what senders 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.

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",
  "isArchived": false,
  "isPaused": false,
  "messages": [
    {
      "object": "message",
      "id": 1,
      "type": "initial",
      "subject": "My subject",
      "replyToID": null,
      "isPaused": false
    },
    {
      "object": "message",
      "id": 2,
      "type": "follow-up",
      "subject": "null",
      "replyToID": 1,
      "isPaused": false
    },
    {
      "object": "message",
      "id": 3,
      "type": "follow-up",
      "subject": "null",
      "replyToID": 1,
      "isPaused": false
    },
    {
      "object": "message",
      "id": 4,
      "type": "drip",
      "subject": "Here's a drip email",
      "replyToID": null,
      "isPaused": false
    },
    {
      "object": "message",
      "id": 5,
      "type": "on-click",
      "subject": "You clicked my link",
      "replyToID": null,
      "isPaused": false
    }
  ],
  "sender": {
    "object": "sender",
    "id": "abc",
    "emailAddress": "mysender@company.com",
    "fromName": "Firm ABC",
    "created": "2017-08-19T02:31:22.218Z"
  },
  "url": "https://mailshake.com/app/#/..."
}

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

Notable fields:

Field Description
isArchived Will be set to true if this campaign is archived. Archived campaigns must be unarchived via the web interface before they can be fully interacted with.

Message

{
  "object": "message",
  "id": 423,
  "type": "initial",
  "subject": "My subject",
  "replyToID": null,
  "isPaused": false
}
{
  "object": "message",
  "id": 424,
  "type": "follow-up",
  "subject": null,
  "replyToID": 423,
  "isPaused": false
}

A message in your campaign such as a follow-up or a drip message.

Notable fields:

Field Description
replyToID Only for follow-up messages, this is the message that indicates the root of the reply chain. If 4 messages will be sent in one reply chain, they’ll all have the same replyToID value.

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.

Sender

{
  "object": "sender",
  "id": "abc",
  "emailAddress": "mysender@company.com",
  "fromName": "Firm ABC",
  "created": "2017-08-19T02:31:22.218Z"
}

A connected email address responsible for sending emails for their associated campaigns.

CreatedLeads

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

The results of an attempt to create leads.

Notable fields:

Key Description
emailsNotFound A list of email addresses that were not turned into leads because they did not match any recipients.
invalidEmails A list of email addresses that were not turned into leads because they did not pass validation.
recipientIDsNotFound A list of recipient IDs that were not found.
isEmpty true if no email addresses or recipient IDs were actually turned into 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,
  "contactID": 1,
  "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,
  "contactID": 1,
  "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": "...",
  "contactID": 1,
}

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,
  "contactID": 1,
  "assignedOnDate": "2017-04-11T16:13:51.955Z",
  "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, lost, 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 wasn’t worth pursuing.
closed The lead turned into a successful interaction.
lost The lead didn’t pan out.

AddRecipientsRequest

{
  "invalidEmails": ["oneexample.com", "twoexample.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.

AddedRecipients

{
  "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",
  "isPaused": false,
  "contactID": 1,
  "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.

CampaignExportRequest

{
  "isEmpty": false,
  "checkStatusID": 1
}

Notable fields:

Key Description
isEmpty true if no campaigns were actually exported.
checkStatusID An ID you can use to monitor the export.

CampaignExport

{
  "isFinished": true,
  "csvDownloadUrl": "https://.../something-unique.csv"
}

Notable fields:

Key Description
isFinished true when the export is done, false to wait a bit and check again.
csvDownloadUrl When finished, this is the url of the CSV file you can download containing your export.

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, your API key will have a few limitations on your usage. You can contact us to request an increase.

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
20 + N
Campaigns > Export
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
3 Activity > Clicks
2 Activity > Opens
1 Activity > Sent
10 Activity > Replies
5 Activity > CreatedLeads
5 Activity > LeadStatusChanges