Create and run a simple campaign
Send an email to one prospect from a connected mailbox, similar to using Compose in the Woodpecker inbox.
Each successful request creates a new campaign with one prospect and one email step, then starts the campaign automatically with the status RUNNING. The email is then processed as part of the mailbox's normal sending flow and is subject to the mailbox's sending limits, so it may not be sent immediately.
Woodpecker reuses an existing prospect with the supplied email address or creates a new one if no match is found. After creation, you can manage the campaign normally using the other campaigns endpoints, including editing it or adding more steps. The mailbox footer is not added automatically; include it in the email content if needed. To respond within an existing conversation, use reply to a message.
Request
Endpoint
POST https://api.woodpecker.co/rest/v2/campaigns/create_and_run_simple_campaign
Headers
x-api-key: {YOUR_API_KEY}
Content-Type: application/json
For details on how to authenticate your requests, please see the authentication guide.
Body
{
"email_account_id": 123456,
"subject": "A quick introduction",
"message": "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
"prospect_email": "richard@piedpiper.com",
"track_opens": false,
"cc_recipient_email": "jared@piedpiper.com",
"bcc_recipient_email": null
}
Body schema
| Field | Type | Required | Description |
|---|---|---|---|
email_account_id | integer | Yes | ID of the SMTP mailbox used to send the email. Get IDs of mailboxes |
subject | string | Yes | Subject of the email |
message | string | Yes | Email content, with support for HTML and Woodpecker snippets, as in an email step |
prospect_email | string | Yes | Email address to send the email to. If the prospect does not exist yet, it will be created automatically |
track_opens | boolean | Yes | Whether to add a tracking pixel to the email and track the email opens |
cc_recipient_email | string/null | No | Email address of the CC recipient. Omit it or set it to null when unused |
bcc_recipient_email | string/null | No | Email address of the BCC recipient. Omit it or set it to null when unused |
Request samples
Create and start a campaign for one prospect
- cURL
- Python
- Java
- Node.js
- PHP
curl --request POST \
--url "https://api.woodpecker.co/rest/v2/campaigns/create_and_run_simple_campaign" \
--header "x-api-key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"email_account_id": 123456,
"subject": "A quick introduction",
"message": "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
"prospect_email": "richard@piedpiper.com",
"track_opens": false,
"cc_recipient_email": "jared@piedpiper.com",
"bcc_recipient_email": null
}'
import requests
def create_and_run_simple_campaign():
url = "https://api.woodpecker.co/rest/v2/campaigns/create_and_run_simple_campaign"
headers = {
"x-api-key": "{YOUR_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"email_account_id": 123456,
"subject": "A quick introduction",
"message": "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
"prospect_email": "richard@piedpiper.com",
"track_opens": False,
"cc_recipient_email": "jared@piedpiper.com",
"bcc_recipient_email": None
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 201:
return response.json()
else:
raise Exception(f"POST request failed: {response.status_code}, {response.text}")
if __name__ == "__main__":
try:
data = create_and_run_simple_campaign()
print("POST response:", data)
except Exception as e:
print("Error:", e)
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
public class WoodpeckerApiClient {
public static void main(String[] args) {
try {
String url = "https://api.woodpecker.co/rest/v2/campaigns/create_and_run_simple_campaign";
String jsonData = """
{
"email_account_id": 123456,
"subject": "A quick introduction",
"message": "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
"prospect_email": "richard@piedpiper.com",
"track_opens": false,
"cc_recipient_email": "jared@piedpiper.com",
"bcc_recipient_email": null
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-api-key", "{YOUR_API_KEY}")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 201) {
System.out.println("POST response: " + response.body());
} else {
throw new Exception("POST request failed: " + response.statusCode() + ", " + response.body());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
const axios = require('axios');
async function createAndRunSimpleCampaign() {
const url = 'https://api.woodpecker.co/rest/v2/campaigns/create_and_run_simple_campaign';
const headers = {
'x-api-key': '{YOUR_API_KEY}',
'Content-Type': 'application/json',
};
const data = {
email_account_id: 123456,
subject: 'A quick introduction',
message: "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
prospect_email: 'richard@piedpiper.com',
track_opens: false,
cc_recipient_email: 'jared@piedpiper.com',
bcc_recipient_email: null,
};
try {
const response = await axios.post(url, data, { headers });
console.log('POST response:', response.data);
} catch (error) {
console.error('POST request failed:', error.response ? error.response.status : error.message);
}
}
createAndRunSimpleCampaign();
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.woodpecker.co/rest/v2/',
'headers' => [
'x-api-key' => getenv('WOODPECKER_API_KEY'),
'Content-Type' => 'application/json',
],
]);
try {
$response = $client->post('campaigns/create_and_run_simple_campaign', [
'json' => [
'email_account_id' => 123456,
'subject' => 'A quick introduction',
'message' => "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
'prospect_email' => 'richard@piedpiper.com',
'track_opens' => false,
'cc_recipient_email' => 'jared@piedpiper.com',
'bcc_recipient_email' => null,
],
]);
echo $response->getStatusCode(), "\n";
echo $response->getBody(), "\n";
} catch (RequestException $e) {
echo "Error: ", $e->getMessage(), "\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getBody(), "\n";
}
}
Response
Response examples
- 201
- 400
- 401
- 422
- 500
The campaign has been created and started; the email will be sent as part of the mailbox's normal sending process and may not be sent immediately.
{
"id": 200001,
"name": "My Campaign",
"status": "RUNNING",
"bounce_shield_autopaused_at": null,
"email_account_ids": [123456],
"settings": {
"timezone": "Europe/Warsaw",
"prospect_timezone": false,
"daily_enroll": 50,
"gdpr_unsubscribe": false,
"list_unsubscribe": false,
"open_disabled_list": null,
"auto_pause_prospect_from_domain_statuses": null,
"auto_pause_prospect_from_domain": null,
"catch_all_verification_mode": "BALANCED",
"count_followup_delay_in_working_days": false
},
"steps": {
"type": "START",
"id": "8c7554ce-a50c-49f7-9129-2ef5a15f9d9c",
"followup": {
"id": "5486ed61-206c-49dc-b394-fb2524cf163e",
"followup": null,
"type": "EMAIL",
"followup_after": {
"range": "DAY",
"value": 1
},
"delivery_time": {
"MONDAY": [{ "from": "00:00", "to": "00:00" }],
"TUESDAY": [{ "from": "00:00", "to": "00:00" }],
"WEDNESDAY": [{ "from": "00:00", "to": "00:00" }],
"THURSDAY": [{ "from": "00:00", "to": "00:00" }],
"FRIDAY": [{ "from": "00:00", "to": "00:00" }],
"SATURDAY": [{ "from": "00:00", "to": "00:00" }],
"SUNDAY": [{ "from": "00:00", "to": "00:00" }]
},
"body": {
"versions": [
{
"id": "a5436b139434744d605261506c5a996f14c4c0411503807579bbc0585d6b9907",
"version": "A",
"subject": "A quick introduction",
"message": "<div>Hi Richard,</div><div>we should talk about Pied Piper!</div>",
"signature": "NO_SIGNATURE",
"track_opens": false
}
]
}
}
}
}
Body schema
The response uses the campaign body schema, with the status RUNNING and a START step followed by one EMAIL step.
The request body is missing or malformed, a required field is missing, or a recipient email address is invalid. The example below shows a missing track_opens field.
{
"type": "validation_error",
"code": "invalid_fields",
"message": "Invalid field(s)",
"request_id": "99943121-e58a-4f02-94d5-9af51bdadbb7",
"fields": [
{
"field": "track_opens",
"issue": "required",
"value": null
}
]
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | validation_error |
code | string | invalid_fields |
message | string | Error message |
request_id | string/null | Request identifier when available |
fields | array | Fields that failed validation |
└─ field | string | Request field name, or body for a missing or malformed body |
└─ issue | string | required or invalid |
└─ value | null | Rejected value, or null when no value was supplied |
Authentication failed. Review the authentication guide and your subscription status.
{
"title": "Unauthorized",
"status": 401,
"detail": "Invalid api key",
"timestamp": "2026-09-20 12:00:00"
}
Body schema
| Field | Type | Description |
|---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string | Error message, such as Invalid api key, The company is inactive, or Upgrade your plan |
timestamp | string | Time of the error in UTC, formatted as YYYY-MM-DD HH:mm:ss |
The mailbox is unavailable for sending, the stored prospects limit is exceeded, or the campaign fails validation before it can run.
An unavailable mailbox, including an ID that does not exist or belongs to another account:
{
"type": "validation_error",
"code": "smtp_not_available_for_sending",
"message": "SMTP account is not available for sending.",
"request_id": "f121d958-5404-4c88-a28a-e6584ab9266c"
}
Campaign validation fails:
{
"type": "validation_error",
"code": "campaign_validation_failed",
"message": "Campaign validation failed.",
"request_id": "c034fbfa-09a0-4f83-9553-10f7a09e4c11",
"fields": [
{
"field": "subject",
"issue": "invalid",
"value": null
}
]
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | validation_error |
code | string | smtp_not_available_for_sending, stored_prospects_limit_exceeded, or campaign_validation_failed |
message | string | Error message |
request_id | string/null | Request identifier when available |
fields | array/null | Present for campaign_validation_failed; lists campaign validation issues |
└─ field | string | Field or campaign setting that failed validation |
└─ issue | string | required or invalid |
└─ value | null | Rejected value, or null when no value was supplied |
Unexpected error, please try again later.
{
"title": "Internal Server Error",
"status": 500,
"detail": null,
"timestamp": "2026-09-20 12:00:00"
}
Body schema
| Field | Type | Description |
|---|---|---|
title | string | A short title describing the error |
status | integer | The HTTP status code |
detail | string/null | Error details when available |
timestamp | string | Time of the error in UTC, formatted as YYYY-MM-DD HH:mm:ss |