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 ] } }
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 states targeted, leaving the zip codes 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.
If your ActionKit instance has fast mailing rebuilds enabled, you can tell the API to choose a fast rebuild if possible by passsing the parameter fast_rebuild=1
in your POST data. If fast rebuilds are off or the mailing has no up-to-date cached base targeting, it will just do a full rebuild instead.
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 |
---|---|
proofs | An integer between 1 and 100, number of samples to send to each reviewer. |
proof_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'],
'proofs': 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.