package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"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")
// Only the fields you set change, and only while the campaign is a
// draft. A ReplyTo that points at "" clears the reply-to address.
response, _, err := client.CampaignsAPI.UpdateCampaign(ctx, accountID, campaignID, requests.UpdateCampaignRequest{
Subject: ahasend.String("Our news for October"),
ReplyTo: ahasend.String(""),
})
if err != nil {
log.Fatalf("Error updating campaign: %v", err)
}
fmt.Printf("Updated campaign %s, %d issues left\n", response.Name, len(response.Issues))
}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";
// Only a draft can change; null clears the reply-to address.
const campaign = await client.campaigns.update(campaignId, {
subject: "What we shipped in October",
reply_to: null,
});
console.log("Campaign updated.", { id: campaign.id, ready: campaign.ready });
curl --request PUT \
--url https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "jsmith@example.com",
"name": "<string>"
},
"reply_to": "jsmith@example.com",
"list_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"content": {
"mjml": "<string>",
"html": "<string>",
"text": "<string>",
"text_is_custom": true
}
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}"
payload = {
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "jsmith@example.com",
"name": "<string>"
},
"reply_to": "jsmith@example.com",
"list_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"content": {
"mjml": "<string>",
"html": "<string>",
"text": "<string>",
"text_is_custom": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(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}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'subject' => '<string>',
'preheader' => '<string>',
'from' => [
'email' => 'jsmith@example.com',
'name' => '<string>'
],
'reply_to' => 'jsmith@example.com',
'list_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'content' => [
'mjml' => '<string>',
'html' => '<string>',
'text' => '<string>',
'text_is_custom' => true
]
]),
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.put("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"subject\": \"<string>\",\n \"preheader\": \"<string>\",\n \"from\": {\n \"email\": \"jsmith@example.com\",\n \"name\": \"<string>\"\n },\n \"reply_to\": \"jsmith@example.com\",\n \"list_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"content\": {\n \"mjml\": \"<string>\",\n \"html\": \"<string>\",\n \"text\": \"<string>\",\n \"text_is_custom\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"subject\": \"<string>\",\n \"preheader\": \"<string>\",\n \"from\": {\n \"email\": \"jsmith@example.com\",\n \"name\": \"<string>\"\n },\n \"reply_to\": \"jsmith@example.com\",\n \"list_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"content\": {\n \"mjml\": \"<string>\",\n \"html\": \"<string>\",\n \"text\": \"<string>\",\n \"text_is_custom\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"object": "campaign",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "noreply@example.com",
"name": "Example Corp"
},
"reply_to": "jsmith@example.com",
"status": "draft",
"status_reason": "<string>",
"send_at": "2023-11-07T05:31:56Z",
"send_resumes_at": "2023-11-07T05:31:56Z",
"finished_at": "2023-11-07T05:31:56Z",
"list_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"editor": "advanced",
"content": {
"text_is_custom": true,
"mjml": "<string>",
"html": "<string>",
"text": "<string>"
},
"ready": true,
"issues": [
{
"code": "sender_missing",
"message": "<string>"
}
]
}{
"message": "Error message"
}{
"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"
}Update a Campaign via the API
Change a draft AhaSend marketing campaign: its name, sender, reply-to, lists, subject, preview text or design. Only a draft campaign can be changed.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"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")
// Only the fields you set change, and only while the campaign is a
// draft. A ReplyTo that points at "" clears the reply-to address.
response, _, err := client.CampaignsAPI.UpdateCampaign(ctx, accountID, campaignID, requests.UpdateCampaignRequest{
Subject: ahasend.String("Our news for October"),
ReplyTo: ahasend.String(""),
})
if err != nil {
log.Fatalf("Error updating campaign: %v", err)
}
fmt.Printf("Updated campaign %s, %d issues left\n", response.Name, len(response.Issues))
}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";
// Only a draft can change; null clears the reply-to address.
const campaign = await client.campaigns.update(campaignId, {
subject: "What we shipped in October",
reply_to: null,
});
console.log("Campaign updated.", { id: campaign.id, ready: campaign.ready });
curl --request PUT \
--url https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "jsmith@example.com",
"name": "<string>"
},
"reply_to": "jsmith@example.com",
"list_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"content": {
"mjml": "<string>",
"html": "<string>",
"text": "<string>",
"text_is_custom": true
}
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}"
payload = {
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "jsmith@example.com",
"name": "<string>"
},
"reply_to": "jsmith@example.com",
"list_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"content": {
"mjml": "<string>",
"html": "<string>",
"text": "<string>",
"text_is_custom": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(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}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'subject' => '<string>',
'preheader' => '<string>',
'from' => [
'email' => 'jsmith@example.com',
'name' => '<string>'
],
'reply_to' => 'jsmith@example.com',
'list_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'content' => [
'mjml' => '<string>',
'html' => '<string>',
'text' => '<string>',
'text_is_custom' => true
]
]),
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.put("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"subject\": \"<string>\",\n \"preheader\": \"<string>\",\n \"from\": {\n \"email\": \"jsmith@example.com\",\n \"name\": \"<string>\"\n },\n \"reply_to\": \"jsmith@example.com\",\n \"list_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"content\": {\n \"mjml\": \"<string>\",\n \"html\": \"<string>\",\n \"text\": \"<string>\",\n \"text_is_custom\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/campaigns/{campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"subject\": \"<string>\",\n \"preheader\": \"<string>\",\n \"from\": {\n \"email\": \"jsmith@example.com\",\n \"name\": \"<string>\"\n },\n \"reply_to\": \"jsmith@example.com\",\n \"list_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"content\": {\n \"mjml\": \"<string>\",\n \"html\": \"<string>\",\n \"text\": \"<string>\",\n \"text_is_custom\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"object": "campaign",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"subject": "<string>",
"preheader": "<string>",
"from": {
"email": "noreply@example.com",
"name": "Example Corp"
},
"reply_to": "jsmith@example.com",
"status": "draft",
"status_reason": "<string>",
"send_at": "2023-11-07T05:31:56Z",
"send_resumes_at": "2023-11-07T05:31:56Z",
"finished_at": "2023-11-07T05:31:56Z",
"list_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"editor": "advanced",
"content": {
"text_is_custom": true,
"mjml": "<string>",
"html": "<string>",
"text": "<string>"
},
"ready": true,
"issues": [
{
"code": "sender_missing",
"message": "<string>"
}
]
}{
"message": "Error message"
}{
"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"
}campaigns:write.
A campaign that is not a draft answers 409 with campaign is not a draft. To change a scheduled campaign, unschedule it first. See Changes only to drafts.
Fields:
- A field that you leave out does not change and is not checked. A request with no field is refused with
400. nullclearssubject,preheader,fromorreply_to. When you clearfrom,reply_tostays.list_idsreplaces all lists of the campaign. A list that does not exist is refused with400.template_idis not accepted: only Create Campaign can copy a campaign template. A request withtemplate_idis refused with400as an unknown field.- The fields follow the rules of Create Campaign.
- You cannot change the editor of the design.
- A campaign with no design yet needs
content.mjmlorcontent.html, which sets its editor. - For a
simpledesign, send onlycontent.text: you can change the design only in the dashboard. - When you send a new
content.mjmlorcontent.htmland leave outcontent.text, a text written by hand stays, and a text made from the HTML is made again from the new HTML. Sendcontent.textasnullto make the text again from the new HTML, also when the text was written by hand. - A text written by hand that stays must still render. If it holds a
{{,{%or{#that cannot be rendered, the request is refused with400. - The content follows the content rules of Create Campaign.
- The API and the dashboard change the same campaign. The request waits for other changes to the same campaign to finish, and a send that starts after the request uses the change.
- When a dashboard editor is open on the same campaign, the design that is saved last is kept.
Idempotency-Key header. A refusal from this operation is stored for the key, so send a corrected request with a new key. See Idempotency and campaign retries.
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
Changes to a draft campaign. A field left out is neither changed nor checked. The design's editor never changes.
Campaign name. Spaces around it are dropped.
1 - 255The subject line, cleaned as on create. Null clears it.
The preview text, cleaned as on create. Null clears it.
The sender, as on create. Null clears the sender; reply_to stays as it is.
Show child attributes
Show child attributes
The reply-to address, as on create. Null clears it.
The lists to send to, which replace the campaign's lists, as on create.
1 - 50 elementsThe content of the design, by the rules of TemplateContentInput, in the design's editor. For a simple design, send only text. A campaign with no design yet needs mjml or html, which sets its editor.
Show child attributes
Show child attributes
Response
Campaign updated
A campaign as List Campaigns returns it, without its lists, content and issues.
Object type identifier
campaign Campaign ID
When the campaign was created
When the campaign's own fields last changed
Campaign name, as it appears in the dashboard. Recipients do not see it.
The subject line. Null until one is set.
The preview text, which most inboxes show after the subject. Null until one is set.
The sender. Null until a complete sender is set, and when its domain was deleted. name is an empty string when the campaign has no sender name, so the object can be sent back as it is.
Show child attributes
Show child attributes
{
"email": "noreply@example.com",
"name": "Example Corp"
}
The reply-to address. Null when none is set.
Where the campaign is in its life. Only a draft can be changed or
deleted.
draft: being prepared.validating: scheduled, and its lists are being checked.waiting_for_review: held for a review before it sends.scheduled: accepted, waiting for its send time.sending: sending now.paused: sending stopped for now;status_reasonsays why.sent: every message was sent.canceled: stopped before it finished;status_reasonsays why.failed: could not be sent;status_reasonsays why.
draft, validating, waiting_for_review, scheduled, sending, paused, sent, canceled, failed Why the campaign is in its status, when there is a reason to give. Null otherwise.
When the campaign sends or sent, in UTC. Null until it is scheduled.
When a send that reached the account's daily marketing limit continues, in UTC. Null otherwise.
When the send ended, in UTC. Null until then.
The lists the campaign sends to, in the order they were saved. A list that was deleted is left out.
The editor of the campaign's design. Null while the campaign has no design.
advanced, simple, html The content of the campaign's design. Null while it has no design, or its design has neither HTML nor text.
Show child attributes
Show child attributes
Whether the draft has no issue, so it can be sent. Null unless the campaign is a draft.
The reasons the draft cannot be sent yet, in a fixed order; empty when it is ready. Null unless the campaign is a draft.
Show child attributes
Show child attributes

