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()
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.DomainsAPI.CreateDomain(ctx, accountID, requests.CreateDomainRequest{
Domain: "example.com",
})
if err != nil {
log.Fatalf("Error creating domain: %v", err)
}
// Publish these records at your DNS provider.
for _, record := range response.DNSRecords {
fmt.Printf("%s %s %s\n", record.Type, record.Host, record.Content)
}
}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 domain = await client.domains.create({ domain: "example.com" });
console.log("Domain created.", { id: domain.id, domain: domain.domain });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/domains \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"domain": "example.com",
"tracking_subdomain": "click",
"return_path_subdomain": "mail",
"subscription_subdomain": "preferences",
"media_subdomain": "media",
"dkim_rotation_interval_days": 45
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/domains"
payload = {
"domain": "example.com",
"tracking_subdomain": "click",
"return_path_subdomain": "mail",
"subscription_subdomain": "preferences",
"media_subdomain": "media",
"dkim_rotation_interval_days": 45
}
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}/domains",
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([
'domain' => 'example.com',
'tracking_subdomain' => 'click',
'return_path_subdomain' => 'mail',
'subscription_subdomain' => 'preferences',
'media_subdomain' => 'media',
'dkim_rotation_interval_days' => 45
]),
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}/domains")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"example.com\",\n \"tracking_subdomain\": \"click\",\n \"return_path_subdomain\": \"mail\",\n \"subscription_subdomain\": \"preferences\",\n \"media_subdomain\": \"media\",\n \"dkim_rotation_interval_days\": 45\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/domains")
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 \"domain\": \"example.com\",\n \"tracking_subdomain\": \"click\",\n \"return_path_subdomain\": \"mail\",\n \"subscription_subdomain\": \"preferences\",\n \"media_subdomain\": \"media\",\n \"dkim_rotation_interval_days\": 45\n}"
response = http.request(request)
puts response.read_body{
"object": "domain",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"domain": "<string>",
"account_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"dns_records": [
{
"type": "<string>",
"host": "<string>",
"content": "<string>",
"required": true,
"propagated": true,
"label": "<string>"
}
],
"last_dns_check_at": "2023-11-07T05:31:56Z",
"dns_valid": true,
"tracking_subdomain": "<string>",
"return_path_subdomain": "<string>",
"subscription_subdomain": "<string>",
"media_subdomain": "<string>",
"dkim_rotation_interval_days": 123,
"dkim_selector": "<string>",
"rotation_ready": true,
"dsn_recipient": "<string>",
"sending_type": "transactional",
"paused": true,
"paused_at": "2023-11-07T05:31:56Z",
"pause_reason": "<string>"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}Add a Sending Domain via the API
Add a sending domain to AhaSend with the API and get the SPF, DKIM, return-path and tracking DNS records to publish, with optional custom subdomains.
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()
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.DomainsAPI.CreateDomain(ctx, accountID, requests.CreateDomainRequest{
Domain: "example.com",
})
if err != nil {
log.Fatalf("Error creating domain: %v", err)
}
// Publish these records at your DNS provider.
for _, record := range response.DNSRecords {
fmt.Printf("%s %s %s\n", record.Type, record.Host, record.Content)
}
}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 domain = await client.domains.create({ domain: "example.com" });
console.log("Domain created.", { id: domain.id, domain: domain.domain });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/domains \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"domain": "example.com",
"tracking_subdomain": "click",
"return_path_subdomain": "mail",
"subscription_subdomain": "preferences",
"media_subdomain": "media",
"dkim_rotation_interval_days": 45
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/domains"
payload = {
"domain": "example.com",
"tracking_subdomain": "click",
"return_path_subdomain": "mail",
"subscription_subdomain": "preferences",
"media_subdomain": "media",
"dkim_rotation_interval_days": 45
}
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}/domains",
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([
'domain' => 'example.com',
'tracking_subdomain' => 'click',
'return_path_subdomain' => 'mail',
'subscription_subdomain' => 'preferences',
'media_subdomain' => 'media',
'dkim_rotation_interval_days' => 45
]),
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}/domains")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"example.com\",\n \"tracking_subdomain\": \"click\",\n \"return_path_subdomain\": \"mail\",\n \"subscription_subdomain\": \"preferences\",\n \"media_subdomain\": \"media\",\n \"dkim_rotation_interval_days\": 45\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/domains")
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 \"domain\": \"example.com\",\n \"tracking_subdomain\": \"click\",\n \"return_path_subdomain\": \"mail\",\n \"subscription_subdomain\": \"preferences\",\n \"media_subdomain\": \"media\",\n \"dkim_rotation_interval_days\": 45\n}"
response = http.request(request)
puts response.read_body{
"object": "domain",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"domain": "<string>",
"account_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"dns_records": [
{
"type": "<string>",
"host": "<string>",
"content": "<string>",
"required": true,
"propagated": true,
"label": "<string>"
}
],
"last_dns_check_at": "2023-11-07T05:31:56Z",
"dns_valid": true,
"tracking_subdomain": "<string>",
"return_path_subdomain": "<string>",
"subscription_subdomain": "<string>",
"media_subdomain": "<string>",
"dkim_rotation_interval_days": 123,
"dkim_selector": "<string>",
"rotation_ready": true,
"dsn_recipient": "<string>",
"sending_type": "transactional",
"paused": true,
"paused_at": "2023-11-07T05:31:56Z",
"pause_reason": "<string>"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}dkim_private_key parameter is currently only supported for Platform Partner accounts.Related Guides
- API authentication, Permission scopes, Domain setup.
- Domain CLI, DNS whitelabel, API errors.
- API rate limits.
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.
255Path Parameters
Account ID
Body
Fully qualified domain name
DKIM Private Key for the domain. Only RSA keys with a minimum key size of 1024 bits are supported.
Note: This parameter is only supported on Platform Partner accounts.
Key size of at least 1024 bits is supported; 2048 bit keys are strongly recommended for improved security.
Optional custom tracking subdomain. Omit to use the default on create. Applies to both manual-DNS and managed-DNS domains.
Optional custom return-path subdomain. Omit to use the default on create. Applies to both manual-DNS and managed-DNS domains.
Optional custom subscription management subdomain. Omit to use the default on create. Applies to both manual-DNS and managed-DNS domains.
Optional custom media subdomain. Omit to use the default on create. Applies to both manual-DNS and managed-DNS domains.
Optional custom DKIM rotation interval in days. Only supported for managed DNS domains on eligible plans.
Optional custom DKIM selector for the domain. Omit, send null, or send an empty or whitespace-only string to use the default selector (no per-domain override); the value is trimmed before it is interpreted, so whitespace-only input is treated as empty.
When provided, it is normalized to lowercase and must be a single DNS label: 1-62 characters using lowercase letters, numbers, and hyphens only, without a leading or trailing hyphen.
Note: This parameter is only supported on Platform Partner accounts.
The type of email that you send from the domain. Omit to create a transactional domain. Null and empty values are refused.
transactional, marketing Response
Domain created successfully
Object type identifier
domain Unique identifier for the domain
When the domain was created
When the domain was last updated
The domain name
Account ID this domain belongs to
DNS records required for domain verification
Show child attributes
Show child attributes
When DNS records were last checked
Whether all required DNS records are properly configured
Custom tracking subdomain. Null means the account or product default is used. Applies to both manual-DNS and managed-DNS domains.
Custom return-path subdomain. Null means the account or product default is used. Applies to both manual-DNS and managed-DNS domains.
Custom subscription management subdomain. Null means the account or product default is used. Applies to both manual-DNS and managed-DNS domains.
Custom media subdomain. Null means the account or product default is used. Applies to both manual-DNS and managed-DNS domains.
Custom DKIM rotation interval in days. Null means the account default is used. Only supported for managed DNS domains.
Per-domain DKIM selector override. Null means no per-domain override is set.
This reports the configured per-domain override only, not the effective selector used for DKIM signing. The effective selector is resolved separately and can differ — for example, a selector locked after DNS verification takes precedence over this override.
Whether the standby DKIM slot is ready for rotation.
Optional recipient address for delivery status notifications.
The type of email that you send from the domain. The sending type affects your deliverability. Marketing email from a transactional domain can get your account paused.
transactional, marketing Whether sending from the domain is paused. While the domain is paused, new email from it is refused, and you cannot delete or rename it. Your other domains continue to send.
When sending from the domain was paused. Null when the domain is not paused.
The reason of the pause. Null when the domain is not paused.
bounce_rate: too many recent emails from the domain bounced.

