package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
campaignID := uuid.MustParse("11111111-1111-4111-8111-111111111111")
response, _, err := client.CampaignsAPI.TestCampaign(ctx, accountID, campaignID, requests.TestCampaignRequest{
To: []string{"Ann <ann@yourdomain.com>", "bob@yourdomain.com"},
})
if err != nil {
log.Fatalf("Error sending test: %v", err)
}
if response.ResultUnknown {
fmt.Println("The test may have gone out; check the inboxes before you send another")
return
}
fmt.Printf("Sent to %v\n", response.SentTo)
for _, failure := range response.Failed {
fmt.Printf("Refused %s: %s\n", failure.Email, failure.Message)
}
}import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const campaignId = "00000000-0000-4000-8000-000000000014";
const result = await client.campaigns.test(campaignId, { to: ["reviewer@example.com"] });
console.log("Campaign test sent.", {
sentTo: result.sent_to,
failed: result.failed.length,
resultUnknown: result.result_unknown,
});
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": [
"<string>"
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test"
payload = { "to": ["<string>"] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "campaign_test",
"sent_to": [
"<string>"
],
"failed": [
{
"email": "<string>",
"message": "<string>"
}
],
"result_unknown": true
}{
"message": "campaign is not ready to send",
"issues": [
{
"code": "subject_missing",
"message": "Add a subject before you send the campaign."
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "campaign is not a draft"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}Send a Campaign Test via the API
Send a test of a draft AhaSend marketing campaign to up to 5 addresses to check it in a real inbox. An account can send 50 test emails per day (UTC).
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
campaignID := uuid.MustParse("11111111-1111-4111-8111-111111111111")
response, _, err := client.CampaignsAPI.TestCampaign(ctx, accountID, campaignID, requests.TestCampaignRequest{
To: []string{"Ann <ann@yourdomain.com>", "bob@yourdomain.com"},
})
if err != nil {
log.Fatalf("Error sending test: %v", err)
}
if response.ResultUnknown {
fmt.Println("The test may have gone out; check the inboxes before you send another")
return
}
fmt.Printf("Sent to %v\n", response.SentTo)
for _, failure := range response.Failed {
fmt.Printf("Refused %s: %s\n", failure.Email, failure.Message)
}
}import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const campaignId = "00000000-0000-4000-8000-000000000014";
const result = await client.campaigns.test(campaignId, { to: ["reviewer@example.com"] });
console.log("Campaign test sent.", {
sentTo: result.sent_to,
failed: result.failed.length,
resultUnknown: result.result_unknown,
});
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": [
"<string>"
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test"
payload = { "to": ["<string>"] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}/test")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "campaign_test",
"sent_to": [
"<string>"
],
"failed": [
{
"email": "<string>",
"message": "<string>"
}
],
"result_unknown": true
}{
"message": "campaign is not ready to send",
"issues": [
{
"code": "subject_missing",
"message": "Add a subject before you send the campaign."
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "campaign is not a draft"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}campaigns:send.
Addresses:
toholds 1 to 5 addresses. Addresses that differ only in case count once.- An entry can have a display name, such as
Ann <ann@example.com>. The test goes to the address only.
- Each address gets the design with placeholders for the contact’s fields.
- The links in the test lead to a contact that does not exist, so a click on the unsubscribe link does not unsubscribe anyone.
- The campaign must be a draft. Any other status answers
409. - A campaign with issues is refused with
400, and the response holds the issues. The issueunsubscribe_link_missingdoes not stop a test. See Issues. - The domain of the sender must be verified and not paused. If not, the test is refused with
400. - An account that is blocked until a person acts, for example a sub-account in the
parent-suspendedstatus, whose parent account is suspended, cannot send tests: the test is refused with400. An account that is blocked only until it pays, for example one with no credits left, can still send tests, because tests use no credits.
- Your account can send 50 test emails per day (UTC). Each address of a test counts as one test email. Test sends from the API and from the dashboard count together.
- The API refuses the whole test when its addresses would take the day’s total over 50. For example, with 48 test emails used, a test to 5 addresses is refused, and a test to 2 addresses is sent.
- The API answers
429when it refuses a test for the limit. Its message currently readsdaily test send limit reached. Backoff does not clear this429. A test to fewer addresses can still fit in the test emails left for the day, as in the example above. Otherwise, the limit clears when the UTC day changes. To plan for it, keep count of the test emails you send each UTC day. A429from Test Campaign that stays after backoff is the daily limit. - An address that the mail server refused still counts, and so does a test with
result_unknown. A test that sent nothing (502) does not count.
sent_tolists the addresses that the mail server accepted, andfailedlists the addresses it refused, each with the reason. An address infailedis given as the mail server reported it, so it can differ from the addresses into.- When
result_unknownistrue, the mail server took the request, but its answer could not be read. The test may have gone out, andsent_toandfailedare empty. Check the inboxes before you send another test. - When the mail server could not take the test, the API answers
502and nothing was sent. Retry the request.
Idempotency-Key header. A 2xx answer is stored, so a retry with the same key replays it and sends nothing again. Unlike other operations, a 4xx answer is not stored. A retry with the same key, for example after the daily limit resets, gets a new answer. A 502 is not stored either, so you can retry it with the same key. See Idempotency.
Related Guides
Authorizations
API key for authentication. Non-empty Security Requirement values are AhaSend API-key roles. Roles listed within one requirement object are jointly required; separate requirement objects are alternatives.
Headers
Optional idempotency key for safe request retries. Must be a unique string for each logical request.
An identical request with a completed stored outcome returns the original status and body. An in-progress
execution returns 409, a changed method/path/body returns 422, and a released 5xx execution may run again.
Keys for non-secret responses expire after 24 hours. API-key create responses include a one-time secret_key,
so successful encrypted replay responses for those operations expire after 5 minutes.
255Body
The addresses to send a test to.
1 to 5 addresses, counted after addresses that differ only in case are dropped. An entry may carry a display name, such as Ann <ann@example.com>; the test goes to the address alone.
1Response
What the test send did. With result_unknown, the test may have gone out.
What a test send did.
Object type identifier
campaign_test The addresses the mail server accepted
The addresses the mail server refused
Show child attributes
Show child attributes
True when the mail server took the request but its answer could not be read. The test may have gone out; sent_to and failed are empty. Check the inboxes before you send another.

