Mailer API - Creating, Targeting, Proofing And Sending Mailings

Overview

The Mailer API provides tools for creating, targeting, proofing and sending mailings. The API provides several endpoints for dealing with mailings. Here's a short overview of them:

URI template Description
/mailer/ create, modify the content and targeting of a mailing
/mailer/{mailing_id}/status/ returns a simplified set of values for checking status
/mailer/{mailing_id}/rebuild/ requests a rebuild of the targeting set
/mailer/{mailing_id}/proofs/ requests proofs of a mailing
/mailer/{mailing_id}/queue/ adds a mailing to the send queue
/mailer/{mailing_id}/progress/ checks the progress of a sending mailing
/mailer/{mailing_id}/stop/ stops (or un-schedules) a sending mailing
/mailer/{mailing_id}/results/ statistics about a sent mailing
/mailer/{mailing_id}/copy/ copies the mailing

Note

The RESTful API provides two endpoints for dealing with mailings:

/mailing/

Provides access to Mailings as a simple mapping. It's low-level and you need to understand the related objects and how to use them in both the database and the API.

/mailer/

Provides access to tools for using mailings - the endpoints here are meant to simplify the common tasks of working with mailings. It's not really a high-level API, but it hides some of the complexity of the ActionKit object model and database.

We will try to consistently refer to this API as the Mailer API to avoid confusion.

Validation And Error Handling

Successful requests will return a 2XX status code. Requests with errors will return a 4XX status code.

400 (Bad Request)

Invalid input results in a response with an HTTP status code 400 (Bad Request). The response will contains a dictionary of errors, associated with each field submitted if possible. Otherwise the body will be an error message, describing the problem.

{ errors: { 'field name': [ error, error, error ] } }

409 (Conflict)

Returned when targeting changes conflict with another set of targeting changes. See PATCH and the description of targeting_version below.

Fields

The full list of fields for the /mailer/ endpoint is below. Additional fields returned by the other endpoints are documented with those endpoints.

Field Description
copy read-only, URI to copy a mailing, see /copy/ below.
created_at read-only, timestamp
custom_fromline a from line only saved with this mailing. Unlike fromline, this field is never saved as a FromLine resource.
emailwrapper URI of a valid EmailWrapper we'll use along with the HTML and Text templates.
excludes inlined MailingTargeting
expected_sent_count read-only, just before sending ActionKit sets this value.
fields list of key / value pairs, Mailing custom fields.
finished_at read-only, timestamp sending finished
fromline the from line for the mailing. NOTE It's always returned as a string, but you can send the URI of a related resource, a dictionary of fields describing a valid FromLine to find or create, or a literal string.
hidden read-only, Boolean
html the Django / ActionKit template for the HTML version of your email.
id read-only, unique integer identifier for this mailing
includes inlined MailingTargeting, see PATCH below for more details.
landing_page URI of a valid Page, NOTE This is required if you use snippets that use Targets based on a User's location. However, unlike in the admin, we do not restrict this value here to specific page types.
lang URI of a valid Language
limit limit the number of users to mail.
limit_percent limit the number of users to mail as a percentage of your full list.
mailing_uri URI of the Mailing resource of this Mailing. You'll need to use this URI if you want to refer to this mailing in calls to other resources, e.g. if you want to create subjects individually.
mails_per_second Throttle the sending rate to this many mails per second, can be floating point value.
mergefile URI reference to a MergeFile resource.
notes notes displayed in the ActionKit admin, or for some other use.
progress read-only, URI to poll the status of a sending mailing, see /progress/ below. Returned in the Location header when a mailing is queued.
proof_users list of references to Users to use for proofs. We will try to generate sample emails for these users.
proofs read-only, URI to request that proofs be sent to reviewers, see /proofs/ below.
query_completed_at read-only, timestamp targeting query was completed
query_previous_runtime read-only, elapsed time of the previous run of the targeting query in seconds.
query_queued_at read-only, timestamp targeting query was added to the queue
query_started_at read-only, timestamp ActionKit started running the targeting query
query_status read-only, current status of the targeting query: none, cleared, failed, queued, rebuilding, saved, invalid.
queue read-only, URI to send or schedule for send, see /queue/ below.
queued_at read-only, the timestamp at which a Mailing was added to the sending queue.
queued_by read-only, URI reference to the auth.User who hit send
rebuild read-only, URI to trigger a rebuild
rebuild_query_at_send boolean, if true the mailing will rebuild the set of users to mail just before sending.
recurring_schedule read-only, a URI reference to the recurring schedule, if this mailing has one
recurring_source_mailing read-only, a URI to the recurring mailing from which this Mailing was copied as part of a recurring send.
reply_to email address to use as the Reply-To header. Be careful what you use here, getting replies from the internet is always super fun.
requested_proofs How many proofs should we send the reviewers when proofs are requested.
resource_uri read-only, the URI of this resource.
results read-only, URI to see the statistics of a sent mailing, see /results/ below.
reviewers list of URIs to Users to receive proofs and a final proof on send. See proofs.
scheduled_by the URI of the AuthUser who scheduled this mailing, displayed in the admin, used for some notifications and history. NOTE Many related fields will accept an integer primary key instead of a URI, references to AuthUser will not.
scheduled_for UTC timestamp, send this mailing as close as possible to this time. Setting this field is not enough to schedule a mailing for send. You must also call queue. Did we mention the timestamp is in UTC? UTC. UTC. UTC.
sort_by Order the users mailed by 'zip' or 'random'. Use zip to email users on the east coast first. Ordering by zip outside of the United States is just silly.
started_at read-only, timestamp sending started
status_uri read-only, URI to poll the status of mailing, see /status/ below.
status read-only, the current status of this mailing: draft, model, sending, scheduled, rebuilding_at_send, recurring, queued, completed, stopped, died
stop read-only, URI to stop a sending mailing, see stop.
submitter URI of an auth.User (an ActionKit Admin). NOTE This user will get notifications via email, and is displayed as the owner of the mailing in the admin. Many related fields will accept a PK instead of a URI, references to AuthUser will not.
tags list of strings. The list will be used to find or create Tag objects that will be associated with the Mailing.
target_mergefile boolean, if True only target users who match the values in the mergefile.
targeting_version read-only, current version of the targeting. Each targeting change increments this value. This is a read-only field, but you can send the value you received with changes and ActionKit will only update the Mailing if the same version is found.
targeting_version_saved read-only, the version of the targeting which was saved as a set of users to mail. If targeting_version != targeting_version_saved, then you will need to request a rebuild.
text the Django / ActionKit template for the Text version of your email. We will generate one automatically for you if you do not set this.
updated_at read-only, timestamp. Lots of processes can update a Mailing, do not rely on this field to know when a mailing was last updated by a real person.
web_viewable boolean, if True this mailing will be viewable on the webs.

Features Not Supported

Some features of mailings are not yet supported in the API:

  • Recurring schedules cannot be created or modified.
  • Auto-excludes are not applied.
  • Test groups are not supported.
  • There is no change tracking for targeting.
  • Default targeting options are not added automatically.

Let us know which you would like to see added first, or if there are other features you would like to see.

Examples

The examples use Python and the Requests library. They do require a little editing to run, specifically to set a user and password.

Endpoints

POST /rest/v1/mailer/

Create a new mailing by POSTing to the list endpoint.

Required fields are:

  • subjects

They are not required, but a minimal working mailing will also need:

  • html
  • fromline

The targeting fields are described in more detail below. The targeting section focuses on PATCHing, but you can create mailings with targeting using POST.

Note

The domain you use in your fromline should be correctly configured in DNS, with correct rDNS, SPF, and DKIM records. Contact us if you are unsure about using a domain.

Example Of Creating A Minimal Mailing

Create a mailing with just fromline, subjects and HTML.

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/mailer/', json={
        "fromline": "Organizer <organizer@example.org>",
        "subjects": [ "Hello world!" ],
        "html": """
<body>
<p>Hello, world!</p>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action!</a></p>
</body>
        """
        })

print "HTTP: %s" % (response.status_code)
print "Location: %s" % (response.headers['location'])

Create A Mailing With A Non-default Email Wrapper, Multiple Subjects, Preview Text, And A Text Template

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

html_tmpl = """
<body>
<p>Hello, world!</p>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action!</a></p>
</body>
"""

text_tmpl = """
Hello, world!

Take some action: http://docs.actionkit.com/signup/signup/

"""

response = client.post(base_url + '/mailer/', json={
    "fromline": "/rest/v1/fromline/1/",
    "subjects": [ "Test subject A", "Test subject B"],
    "preview_text": [ "Preview A", "Preview B"],
    "emailwrapper": "/rest/v1/emailwrapper/1/",
    "html": html_tmpl,
    "text": text_tmpl,
    "submitter": "/rest/v1/authuser/1/"
    })

print "HTTP: %s" % (response.status_code)
print "Location: %s" % (response.headers['location'])

Create A Mailing With Targeting Using A Query Report.

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

client.post(base_url + "/queryreport/", json=dict(
    name="Users By Pant Style",
    short_name="users_by_pant_style",
    sql="SELECT DISTINCT parent_id FROM core_userfield WHERE name = 'pants_style' and value = {style}"
    ))

html_tmpl = """
<body>
<p>Hello, pants lovers!</p>
<p><a href="http://docs.actionkit.com/sign/saveourpants/">Save some pants today!</a></p>
</body>
"""

response = client.post(base_url + '/mailer/', json={
    "fromline": '"Fancy Pants" <fancypants@example.com>',
    "subjects": [ "Velour!", "Wide whale cordoruy!", "Pleated!" ],
    "includes": {
       "users": [ 2,3,4 ]
    },
    "excludes": {
       "query_reports": [ { 'short_name': "users_by_pant_style", 'params': { 'style': 'fancy' } } ]
    },
    "html": html_tmpl
    })

print "HTTP: %s" % (response.status_code)
print "Location: %s" % (response.headers['location'])

PATCH /rest/v1/mailer/{mailing_id}/

Update single fields of a mailing, requires at least one field to update. PATCH can have side-effects, be sure to read the whole section on targeting!

Example Of PATCHing Multiple Fields

Update the HTML template, notes and multiple fields using PATCH.

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/mailer/', json={
        "custom_fromline": "Organizer <organizer@example.org>",
        "subjects": [ "Hello world!" ],
        "html": """
<body>
<p>Hello, world!</p>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action!</a></p>
</body>
        """
        })

mailing_uri = response.headers['location']

html_tmpl = """
<body>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action.</a></p>
</body>
"""
response = client.patch(mailing_uri, json=
    { "html": html_tmpl })
print "HTTP: %s" % (response.status_code)

response = client.patch(mailing_uri, json={
     "notes": "Recruitment Test version 1.23 2015" })
print "HTTP: %s" % (response.status_code)

response = client.patch(mailing_uri, json={
  "html_tmpl": """ Placeholder content """,
  "reply_to": "Help Organization <help@example.org>",
  "notes": "Recruitment Test version 1.23 2015" })
print "HTTP: %s" % (response.status_code)

response = client.patch(mailing_uri, json={
    "subjects": ["Subject Two",
                 "Subject Three",
                 "Subject Four" ] })
print "HTTP: %s" % (response.status_code)

Targeting

You can PATCH subsets of targeting fields as well. The values of the fields you PATCH are replaced with new values, but the other targeting fields will be left unchanged.

Updating targeting fields in a request will trigger several events:

  • invalidate the saved query
  • invalidate the saved queries of any mailings that include or exclude this mailing

You'll need to think carefully about how your mailing practices may be affected by API calls that clear saved sets.

Here is an example of updating a single targeting option in a mailing that has multiple targeting criteria. The PATCH will only update the zip codes targeted, leaving the states unchanged.

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/mailer/', json={
        "custom_fromline": "Organizer <organizer@example.org>",
        "subjects": [ "Hello world!" ],
        "html": """
<body>
<p>Hello, world!</p>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action!</a></p>
</body>
        """,
        "includes": {
            "states": [ 'CA', 'TX', 'NJ' ],
            "zips": [ "10014", "19081", "00215" ]
        }}
    )

mailing_uri = response.headers['location']

response = client.patch(mailing_uri, json={
    "includes": {
        "states": [ "PA", "NY", "MA" ]
        }
     })
print "HTTP: %s" % (response.status_code)

Using Targeting_version To Detect Changes

The field targeting_version is an integer used to track changes to targeting.

If you include targeting_version in your PATCH request, ActionKit will check that the targeting version in the database still matches what you sent before updating. If the versions do not match, a 409 CONFLICT response will be returned. If you don't send targeting_version, ActionKit will not check the version. In both cases, we'll increment the targeting_version after the update.

Targeting fields are defined as anything in the includes or excludes fields and

  • limit
  • limit_percent
  • sort_by
  • target_group_from_landing_page
  • target_mergefile
  • mails_per_second

We try to detect when targeting values have changed, to avoid needlessly invalidating mailings.

import requests

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/mailer/', json={
        "custom_fromline": "Organizer <organizer@example.org>",
        "subjects": [ "Hello world!" ],
        "html": """
<body>
<p>Hello, world!</p>
<p><a href="http://docs.actionkit.com/signup/signup/">Take some action!</a></p>
</body>
        """,
        "includes": {
            "states": [ 'CA', 'TX', 'NJ' ],
            "zips": [ "10014", "19081", "00215" ]
        }}
    )

mailing_uri = response.headers['location']

response = client.get(mailing_uri)
mailing  = response.json()
print "Initial version:  %s" % (mailing['targeting_version'])

response = client.patch(mailing_uri, json={
    "includes": {
        "states": [ "PA", "NY", "MA" ]
        }
     })

response = client.get(mailing_uri)
mailing  = response.json()
print "Modified version: %s" % (mailing['targeting_version'])

response = client.patch(mailing_uri, json={
    "includes": { "states": [ "WI", "MN", "MI" ] },
    "targeting_version": 4791, # not valid, so request will fail.
     })
print "HTTP: %s" % (response.status_code)

Identifiers In Targeting

You can provide several different unique identifiers for related resources. For example, you can send references to Pages using the 'name' or 'id' fields, or using the URI returned by the RESTful API endpoints. All of the following are valid references to the example USDA petition:

'usda', 1, '/rest/v1/page/1/'

You can combine the different styles in a single list:

1, 'unsubscribe', '/rest/v1/page/5/'

However, the API will return the "name" field in the targeting options. For example, if you were to send:

"includes": { "actions": [ "unsubscribe", 4, "/rest/v1/petitionpage/1/" ] }

The API would return:

"includes": {"actions": ["usda", "ohiomilk", "unsubscribe"] }

Full List Of Targeting Fields

A full list of targeting fields, for includes and excludes is below.

Field name Description
actions List of Pages
campaign_radius Radius in miles.
campaign_samestate_only Limit radius matches to same U.S. state as the event.
campaign_same_county_only Limit radius matches to same U.S. county as the event.
campaign_same_district_only Limit radius matches to same U.S. Congressional district as the event.
campaigns List of Campaign names (name field on event_campaign), find members near events in these Campaigns.
cds List of U.S. Congressional Districts, formatted as two-letter state, underscore, two digit zero-padded district number: OH_01, CA_23, TX_10.
counties List of U.S. County names.
countries List of country names.
has_donated Boolean.
languages List of language identifiers. These are ActionKit Language identifiers, i.e. IDs or names from /rest/v1/language/, but not the iso_code.
lists List of lists.
mailings List of mailings.
query_reports List of dictionaries, each with a short_name and parameters. For example, [ { 'short_name': 'users', 'params': { 'modulus': 3 } } ]
raw_sql List of SQL statements.
regions List of regions, e.g. Ontario, Canada; Quebec, Canada
state_house_districts List of U.S. state house districts, formatted as two letter state, underscore, three digit zero-padded district number, e.g. TX_012.
state_senate_districts List of U.S. state senate seats, formatted as two letter state, underscore, three digit zero-padded district number, e.g. TX_012.
states List of U.S. states, the format is the two-letter state code, e.g. RI
tags List of tag identifier, e.g. 3 or /rest/v1/tag/3/ or 'loves-fuzzy-animals'.
users List of User identifiers.
was_monthly_donor List of monthly donor "states": 'active', 'any', 'expires_this_month', 'expired_last_month', 'canceled_last_7_days'. See 'code' column in core_recurringdonortargetingoption table.
zip_radius Radius in miles around places in zips.
zips List of places, takes all formats the event search box will, including zips, e.g. 90210, "Macon, Georgia", "Moscow", "[some French postcode], France"

More Examples of PATCHing Targeting

This example shows how to targets users within a radius of a list of places, constituents of members of a target group, and how to include or excludes lists. The combined targeting is pretty much guaranteed to not match any users!

# -*- coding: utf-8 -*-

import requests
import pprint

base_url = 'https://docs.actionkit.com/rest/v1'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

#
# Create a mailing, we'll PATCH the targeting
#
response = client.post(base_url + '/mailer/', json={
    "fromline": '"Fancy Pants" <fancypants@example.com>',
    "subjects": [ "Hello!", "こんにちは!", "Hola!", "Grüß Gott!" ],
    "html": """
<body>
<p>Hello, Everybody!</p>
<p><a href="http://docs.actionkit.com/sign/sayhey/">Say Hello Today!</a></p>
</body>
"""
    })

if response.status_code != 201:
    raise Exception(response.content)

#
# OK! Created! Save the address for our new mailing.
#
mailing_uri = response.headers['location']

#
# Target users within 90 miles of a random list of cities
#
response = client.patch(mailing_uri, json={
    'includes': {
        'zips': [
            'Londonderry Township, Pennsylvania, USA',
            'Athens, Alabama, USA',
            'Plymouth, Massachusetts, USA',
            'Delta, Pennsylvania, USA',
            'Scriba, New York, USA',
            'Waterford, Connecticut, USA',
            'Crystal River, Florida, USA',
            'Vernon, Vermont, USA'
        ],
        'zip_radius': 90
    }})

if response.status_code != 202:
    raise Exception(response.content)

#
# Target users who are constituents of (current as of 09/2015) members of the Senate
# Foreign Relations Committee
#
response = client.get(base_url + '/target/', params=dict(
    type='senate',
    seat__in='TN_1,CA_1,MD_1,ID_1,FL_2,NJ_1,WI_2,NH_2,AZ_2,DE_2,CO_2,NM_2,GE_1,CT_2,GE_2,VA_1,KY_2,MA_1,WY_2',
    _limit=20
))
if response.status_code != 200:
    raise Exception(response.content)
results = response.json()
targets = map(lambda t: t['resource_uri'], results['objects'])

response = client.post(base_url + '/congresstargetgroup/', json=dict(
            name="Senate Foreign Relations Committee Two",
            type='senate',
            targets=targets
            ))
if response.status_code != 201:
    raise Exception(response.content)

target_group = response.headers['location']

response = client.patch(mailing_uri, json={
    'includes': {
        'target_groups': [ target_group ] }})
if response.status_code != 202:
    raise Exception(response.content)

response = client.get(mailing_uri)
if response.status_code != 200:
    raise Exception(response.content)

#
# Targeting users subscribed to a specific List, while excluding
# users on another List
#
response = client.patch(mailing_uri, json=({
    'includes': {
        'lists': [ "Supporters of Mammals in the Sea" ],
        },
    'excludes': {
        'lists': [ "Supporters of Mammals on the Land" ]
        }}))

# If the lists don't exist you'll get an error:
#  {"error": "[u\"Unable to set field lists to [u'Supporters of Mammals on Land']: [u'No such List: Supporters of Mammals on Land']\"]"}
if response.status_code != 202:
    raise Exception(response.content)

response = client.get(mailing_uri)
if response.status_code != 200:
    raise Exception(response.content)

POST /rest/v1/mailer/{mailing_id}/rebuild/

Requests a rebuild of the targeting, generating a saved set of users and a count. The API will not launch a rebuild automatically when the targeting is updated, but it will invalidate the existing saved set - to make sure you do not email the wrong users.

You don't need to construct this URI, it's returned with each mailing in the value of the 'rebuild' field.

Returns HttpCreated on success. Use the URI in the Location header to poll for completion. The status URI returns a dictionary of the following values. You should check for finished = True in your polling code.

key type values
errors list on failure, a list of errors
finished boolean true, false
finished_at timestamp when the rebuild finished
results integer on success, count of users targeted by the mailing.
started_at timestamp when the rebuild started
success boolean true, false

Example Of Response On Success

{"errors": [],
 "success": true,
 "finished_at": "2015-09-29T08:02:22",
 "results": 32,
 "finished": true,
 "started_at": "2015-09-29T08:02:19"}

Example Of Response On Failure

{"errors": ["OperationalError: (1054, \\"Unknown column \'action_id\' in \'where clause\'\\")"],
 "success": false,
 "finished_at": "2015-09-29T08:07:51",
 "results": null,
 "finished": true,
 "started_at": "2015-09-29T08:07:47"}

Example Of Requesting And Waiting For A Rebuild

import requests
import time
import pprint

base_url = 'https://docs.actionkit.com/'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/rest/v1/mailer/', json={
    "fromline": '"Fancy Pants" <fancypants@example.com>',
    "subjects": [ "Welcome to the world of pleats!" ],
    "includes": {
       "query_reports": [ { 'short_name': "new_to_list", 'params': { 'days': '15', 'list': 1 } } ]
    },
    "html": """
<body>
<p>Hello, new pants lovers!</p>
<p><a href="http://docs.actionkit.com/sign/saveourpants/">Let's save some more pants together!</a></p>
</body>
"""
    })
if response.status_code != 201:
    raise Exception(response.content)

response = client.get(response.headers['location'])
if response.status_code != 200:
    raise Exception(response.content)

mailing = response.json()

#
# We can POST the request for a rebuild using the URI in "rebuild". Note that
# the URIs in the body are relative, so you'll need to prefix with your base URL.
#
response = client.post(base_url + mailing['rebuild'])
if response.status_code != 201:
    raise Exception(response.content)

print "Rebuild submitted, going to poll for response"

#
# The response has a URI we can use to wait for the rebuild to complete.
#
status_uri = response.headers['location']

for i in range(3600):
    response = client.get(status_uri)
    status   = response.json()

    if status['finished']:
        break

    print "Waiting a second, %d." % (i)
    time.sleep(1.0)

pprint.pprint(status)

GET /rest/v1/mailer/{mailing_id}/status/

Returns a simplified view of the mailing with only status fields included.

You don't need construct this URI, it's returned with each mailing in the value of the 'status_uri' key.

field type values
status string draft, rebuilding, sending, completed, model, scheduled, recurring, died, stopped
error list of strings null, a list of errors

Other fields are included depending on the status of the mailing:

status additional fields
draft query_status, query_previous_runtime, count
rebuilding query_queued_at, query_started_at, query_previous_runtime, query_status,
sending queued_at, queued_by, started_at, finished_at, rate, progress, expected_send_count
completed count
recurring scheduled_by, recurring_schedule, recurring_source_mailing
scheduled scheduled_by, scheduled_for

Example Status Response

{"count": 0,
 "error": null,
 "query_previous_runtime": 0,
 "query_status": "saved",
 "status": "draft"}

GET /rest/v1/mailer/{mailing_id}/progress/

Returns rate and progress fields when a mailing is sending. Counts are never updated more than every 10 seconds.

You don't need construct this URI, it's returned as the Location header in the response to a /queue/ request. See queue for an example of using the returned status.

field type values
started_at timestamp when the send started
rate integer mails per second
progress integer mails sent so far
expected_send_count integer mails we expect to send

POST /rest/v1/mailer/{mailing_id}/proofs/

Request proofs for this draft mailing, sending the proofs to the emails listed in reviewers.

( We don't return proofs in-line yet. Emails are not just HTML, and you really need to look at them in email clients. But we hope to add a preview function at some point to return the rendered MIME message. Submit a feature request if you'd like to see that! )

Field Description
count An integer between 1 and 100, number of samples to send to each reviewer.
users Optional, generate proofs for these specific users.
date Optional, for recurring mailings, pretend it's this date (if possible!)
reviewers Optional, a list of emails to which we will send the proofs.

Either reviewers needs to be sent with the first request for proofs request, or the Mailing needs to have a submitter assigned. You can send reviewers with every request for proofs too.

Returns HttpCreated, the Location header has a URI to poll for progress and errors.

Example Of Requesting Proofs

#!/bin/env python

import requests
import time
import pprint

base_url = 'https://docs.actionkit.com/'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/rest/v1/mailer/', json={
    "fromline": '"Fancy Pants" <fancypants@example.com>',
    "subjects": [ "Welcome to the world of pleats!" ],
    "limit": 100,
    "html": """
<body>
<p>Hello, new pants lovers!</p>
<p><a href="http://docs.actionkit.com/sign/saveourpants/">Let's save some more pants together!</a></p>
</body>
"""
    })
if response.status_code != 201:
    raise Exception(response.content)

response = client.get(response.headers['location'])
if response.status_code != 200:
    raise Exception(response.content)

mailing = response.json()

#
# We can POST the request for a rebuild using the URI in "rebuild".
#
response = client.post(base_url + mailing['rebuild'])
if response.status_code != 201:
    raise Exception(response.content)

#
# The response has a URI we can use to wait for the rebuild to complete.
#
status_uri = response.headers['location']

for i in range(3600):
    response = client.get(status_uri)
    status   = response.json()

    if status['finished']:
        break

    print "Waiting a second, %d." % (i)
    time.sleep(1.0)


print "Count completed!"
pprint.pprint(status)

#
# Get some proofs! You can only successfully get proofs if the mailing is a
# draft *and* has a saved set of users. POST to /rebuild/ before /proofs/ if
# needed.
#
response = client.post(base_url + mailing['proofs'], json={
        'reviewers': ['youremailaddress@example.com'],
        'count': 5
    })
if response.status_code != 201:
    raise Exception(response.content)

status_uri = response.headers['location']

for i in range(3600):
    response = client.get(status_uri)
    status   = response.json()

    if status['finished']:
        break

    print "Waiting a second, %d." % (i)
    time.sleep(1.0)


print "Proofs sent!"
pprint.pprint(status)

POST /rest/v1/mailer/{mailing_id}/queue/

Queue a mailing for immediate or scheduled sending. If the mailing has a scheduled_for field, the mailing will send at that time.

Warning

Really sends. No confirmation. No haiku. Mailing just gets added to the queue and goes.

Note

The API does not currently handle recurring mailings, so you cannot call queue on a mailing with a recurring schedule.

Returns HttpCreated, the Location header has a URI to poll for progress. The returned fields are never updated more than every 10 seconds:

field type values
finished boolean true or false
status string queued, sending, died, stopped, scheduled
progress integer number of mails sent so far
rate float rate per second
started_at timestamp when the mailing started sending
expected_send_count integer number of mails we expect to send

Example Of Queueing An Immediate Send

#!/bin/evn python

import requests
import time
import pprint

base_url = 'https://docs.actionkit.com/'

client = requests.Session()
client.auth = ('user', 'password')
client.headers.update({'content-type': 'application/json',
                       'accepts': 'application/json'})

response = client.post(base_url + '/rest/v1/mailer/', json={
    "fromline": '"Fancy Pants" <fancypants@example.com>',
    "subjects": [ "Welcome to the world of pleats!" ],
    "limit": 300,
    "html": """
<body>
<p>Hello, new pants lovers!</p>
<p><a href="http://docs.actionkit.com/sign/saveourpants/">Let's save some more pants together!</a></p>
{% sleep 1000 %}
</body>
"""
    })
if response.status_code != 201:
    raise Exception(response.content)

response = client.get(response.headers['location'])
if response.status_code != 200:
    raise Exception(response.content)

mailing = response.json()

response = client.post(base_url + mailing['rebuild'])
if response.status_code != 201:
    raise Exception(response.content)

status_uri = response.headers['location']

for i in range(3600):
    response = client.get(status_uri)
    if response.status_code != 200:
        raise Exception(response.content)
    status   = response.json()

    if status['finished']:
        break

    time.sleep(1.0)

#
# It's a dangerous example. You'll have to remove the safety.
#
raise Exception("Safety enabled. This code will really send to your actual users if you remove this safety.")

response = client.post(base_url + mailing['queue'])
if response.status_code != 201:
    raise Exception(response.content)

progress_uri = response.headers['location']

for i in range(300):
    response = client.get(progress_uri)
    status   = response.json()

    if status['finished']:
        break

    if status['progress']:
        print "%d / %d, %f/s" % (status['progress'],status['expected_send_count'], status['rate'])
    else:
        print " waiting to start "

    time.sleep(10) # remember, only updated every 10 seconds.

pprint.pprint(status)

POST /rest/v1/mailer/{mailing_id}/stop/

Send a signal to a sending, scheduled, or recurring mailing to stop. A sending mailing may take several seconds to actually stop.

Calling stop on a scheduled mailing will return it to draft status.

Calling stop on a recurring mailing will return it to a draft status.

GET /rest/v1/mailer/{mailing_id}/results/

Returns basic result numbers for the mailing. Use reports if you want more control over the information you get back.

Returns totals and subject breakdowns of sends, opens, clicks, actions, complaints, unsubscribes and more.

POST /rest/v1/mailer/{mailing_id}/copy/

Copy this mailing.

You can send two parameters to control the behavior of copy:

Name Type Description
targeting boolean copy the targeting along with the content, defaults to True.
keep_test_group boolean include the mailing in the same test group, defaults to False.

Returns HttpCreated, the Location header has the URI of the new mailing.