API reference Markdown

Steps and variants

A step is one email of the sequence. Each step has one to four variants, labelled A to D, for A/B tests. The content of a step is its variant A.

Method Path Action
GET /api/v1/campaigns/{id}/steps List the steps with their variants
POST /api/v1/campaigns/{id}/steps Add a step at the end
PATCH /api/v1/campaigns/{id}/steps/{stepId} Edit a step
DELETE /api/v1/campaigns/{id}/steps/{stepId} Delete a step
POST /api/v1/campaigns/{id}/steps/{stepId}/variants Add a variant
PATCH /api/v1/campaigns/{id}/steps/{stepId}/variants/{label} Edit a variant
DELETE /api/v1/campaigns/{id}/steps/{stepId}/variants/{label} Delete a variant

How writes work#

Every write reads the whole sequence, changes one element and saves the whole sequence with the function of the sequence editor in the app. The validation of variants, the cleaning of HTML, the step schedules and the other nodes of the sequence (tasks and conditions made in the app) follow the editor rules. The API has no content rules of its own.

  • Writes are allowed on a draft or a paused campaign. A running campaign returns 409. Pause it first.
  • Every write returns the whole sequence in the shape of GET /steps.
  • A step is identified by its position, as in the editor. After you delete step 2, the old step 3 becomes step 2 and takes the id of the old step 2. Read the ids again from the response after a delete.
  • Snippets such as {{first_name}} and {{company}} are filled from the prospect when the email goes out. A missing value blocks the start.

List steps#

curl "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN"
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
  },
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "GET",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
)
print(response.status_code, response.text)

Response 200

{
  "data": [
    {
      "id": "a1b2c3d4-0000-4000-8000-000000000001",
      "position": 1,
      "name": null,
      "delay_days": 0,
      "subject": "Quick question, {{first_name}}",
      "body": "Hi {{first_name}},\n\nAre you the right person to talk to about outbound at {{company}}?",
      "content_format": "plain",
      "append_signature": true,
      "preview_text": null,
      "schedule": null,
      "variants": [
        {
          "label": "A",
          "subject": "Quick question, {{first_name}}",
          "body": "Hi {{first_name}},\n\nAre you the right person to talk to about outbound at {{company}}?",
          "content_format": "plain",
          "append_signature": true,
          "active": true,
          "preview_text": null
        },
        {
          "label": "B",
          "subject": "{{first_name}}, a question about {{company}}",
          "body": "Hi {{first_name}},\n\nAre you the right person to talk to about outbound at {{company}}?",
          "content_format": "plain",
          "append_signature": true,
          "active": true,
          "preview_text": null
        }
      ]
    }
  ]
}
Field Notes
delay_days Days after the previous step. Step 1 usually has 0.
subject An empty subject on a follow-up keeps the email in the thread of the previous one.
content_format plain or html. HTML is cleaned when it is saved.
append_signature Add the signature of the sending mailbox.
schedule The send window of this step, or null to follow the campaign schedule.
variants[].active An inactive variant is kept but not sent.

Errors: 404 with Campaign not found.

Add a step#

POST /api/v1/campaigns/{id}/steps adds a step at the end of the sequence. Without delay_days, the first step gets 0 and later steps get 2.

Body (JSON, up to 64 KiB). All fields are optional. An unknown field returns 400.

Field Type Notes
subject string
body string
name string or null A label for the step in the app.
delay_days integer 0 or more.
content_format string plain or html.
append_signature boolean
preview_text string or null The preview line of the inbox.
schedule object or null A step schedule in the shape that GET returns.
curl -X POST "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject":"","body":"Hi {{first_name}}, a short follow-up on my last note.","delay_days":3}'
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"subject": "", "body": "Hi {{first_name}}, a short follow-up on my last note.", "delay_days": 3}),
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "POST",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
    json={'subject': '', 'body': 'Hi {{first_name}}, a short follow-up on my last note.', 'delay_days': 3},
)
print(response.status_code, response.text)

Response 201 returns the whole sequence.

Errors

Status Message
400 Unknown field(s): …, `delay_days` must be a number., `content_format` must be plain or html.
404 Campaign not found.
409 Steps can be edited only on a draft or a paused campaign — this one is active. Pause it first.
409 A rule of the sequence editor, for example Add at least one email step with a subject and a body. or Step 2 schedule: …

Edit a step#

PATCH /api/v1/campaigns/{id}/steps/{stepId} takes the fields of the add call. subject, body, content_format, append_signature and preview_text change variant A. Variants B to D stay as they are. name, delay_days and schedule change the step.

curl -X PATCH "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"delay_days":4}'
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"delay_days": 4}),
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "PATCH",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
    json={'delay_days': 4},
)
print(response.status_code, response.text)

Response 200 returns the whole sequence. Errors: as for add, plus 404 with Step not found.

Delete a step#

DELETE /api/v1/campaigns/{id}/steps/{stepId} removes a step. The steps after it move up one position. The response is 200 with the whole sequence.

curl -X DELETE "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN"
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
  },
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "DELETE",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000002",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
)
print(response.status_code, response.text)

Errors: 404 with Campaign not found. or Step not found., 409 with A campaign needs at least one step; edit this one instead.

Add a variant#

POST /api/v1/campaigns/{id}/steps/{stepId}/variants adds the next free label: B, then C, then D. Fields you omit are copied from the first variant. A new variant is active unless you send "active": false.

Field Type
subject string
body string
content_format plain or html
append_signature boolean
preview_text string or null
active boolean
curl -X POST "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject":"{{first_name}}, a question about {{company}}"}'
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"subject": "{{first_name}}, a question about {{company}}"}),
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "POST",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
    json={'subject': '{{first_name}}, a question about {{company}}'},
)
print(response.status_code, response.text)

Response 201 returns the whole sequence.

Errors: 404 with Step not found., 409 with A step can have at most 4 variants.

Edit or delete a variant#

PATCH /api/v1/campaigns/{id}/steps/{stepId}/variants/{label} takes the fields of the add call. {label} is A to D and is not case-sensitive. To stop sending a variant without losing it, send {"active": false}.

DELETE /api/v1/campaigns/{id}/steps/{stepId}/variants/{label} removes a variant. The last variant of a step cannot be deleted. Delete the step instead.

curl -X PATCH "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants/B" \
  -H "Authorization: Bearer $ANYMAIL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
const res = await fetch("https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants/B", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ANYMAIL_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"active": false}),
});
console.log(res.status, await res.text());
import os
import requests

response = requests.request(
    "PATCH",
    "https://app.anymail.watch/api/v1/campaigns/3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/steps/a1b2c3d4-0000-4000-8000-000000000001/variants/B",
    headers={
        "Authorization": f"Bearer {os.environ['ANYMAIL_TOKEN']}",
    },
    json={'active': False},
)
print(response.status_code, response.text)

Both return 200 with the whole sequence.

Errors: 404 with Variant not found., 409 with A step needs at least one variant; delete the step instead.