Create Domain
Register a domain to send from and get the DNS records to publish at its DNS provider. The domain starts pending and becomes verified once the required records are found.
The call is idempotent: registering a domain your organization already has returns its current state, so it responds 200 OK rather than 201 Created.
After publishing the records, call Verify Domain to check them straight away. Arsel also checks every pending domain about every 15 minutes. See Domain Verification for how to add the records at your DNS provider.
Endpoint
POST /email/domains
Returns: 200 OK
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <your-api-key> | Yes |
Content-Type | application/json | Yes |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The domain to send from, e.g. mail.acme.com. Max 255 characters. Stored lowercased. |
{
"name": "mail.acme.com"
}
Register one domain at a time. A second registration sent while another is still in progress for your organization is refused with 409.
Response
{
"id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a",
"name": "mail.acme.com",
"status": "pending",
"verified_at": null,
"created_at": "2026-03-08T12:00:00.000Z",
"records": [
{
"type": "CNAME",
"name": "4hbfxq3v2nkd6wz5ymtj7ra2ue3lc5po._domainkey.mail.acme.com",
"value": "4hbfxq3v2nkd6wz5ymtj7ra2ue3lc5po.dkim.amazonses.com",
"purpose": "dkim",
"required": true,
"status": "pending"
},
{
"type": "CNAME",
"name": "s7kq2mwx9pzr4tbv6ny3hd8cgj5fa2le._domainkey.mail.acme.com",
"value": "s7kq2mwx9pzr4tbv6ny3hd8cgj5fa2le.dkim.amazonses.com",
"purpose": "dkim",
"required": true,
"status": "pending"
},
{
"type": "CNAME",
"name": "zt5c8rjd2wqy7mhn4xkb9vp3ga6fe2us._domainkey.mail.acme.com",
"value": "zt5c8rjd2wqy7mhn4xkb9vp3ga6fe2us.dkim.amazonses.com",
"purpose": "dkim",
"required": true,
"status": "pending"
},
{
"type": "TXT",
"name": "mail.acme.com",
"value": "v=spf1 include:amazonses.com ~all",
"purpose": "spf",
"required": false,
"status": "pending"
},
{
"type": "TXT",
"name": "_dmarc.mail.acme.com",
"value": "v=DMARC1; p=none;",
"purpose": "dmarc",
"required": false,
"status": "pending"
}
]
}
Domain fields
| Field | Type | Description |
|---|---|---|
id | string | The domain ID |
name | string | The domain name |
status | string | pending, verified or failed. Only a verified domain can send. |
verified_at | string | null | When the domain was verified (ISO 8601) |
created_at | string | When the domain was added (ISO 8601) |
records | array | The DNS records to publish at the domain's DNS provider |
Record fields
| Field | Type | Description |
|---|---|---|
type | string | DNS record type, e.g. CNAME or TXT |
name | string | The record's host name, fully qualified. Some DNS providers want only the part before your domain. |
value | string | The record's value |
purpose | string | dkim, spf, dmarc or mail_from |
required | boolean | The domain cannot become verified until every required record is published. The others are recommended for deliverability. |
status | string | pending or verified, as of the last check |
Examples
- cURL
- JavaScript
- Python
- C#
- PHP
curl -X POST "https://api.arsel.sa/v1/email/domains" \
-H "Authorization: Bearer $ARSEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "mail.acme.com"
}'
const response = await fetch("https://api.arsel.sa/v1/email/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ARSEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "mail.acme.com",
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(`${data.name}: ${data.message}`);
}
console.log(data);
import os
import requests
response = requests.post(
"https://api.arsel.sa/v1/email/domains",
headers={"Authorization": f"Bearer {os.environ['ARSEL_API_KEY']}"},
json={
"name": "mail.acme.com",
},
)
response.raise_for_status()
print(response.json())
using System.Net.Http.Headers;
using System.Text;
var client = new HttpClient(); // share one instance across requests
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("ARSEL_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.arsel.sa/v1/email/domains")
{
Content = new StringContent(
"""
{
"name": "mail.acme.com"
}
""",
Encoding.UTF8,
"application/json"),
};
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)response.StatusCode}: {body}");
Console.WriteLine(body);
<?php
$ch = curl_init("https://api.arsel.sa/v1/email/domains");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ARSEL_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "mail.acme.com",
]),
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) >= 400) {
throw new Exception("{$data['name']}: {$data['message']}");
}
print_r($data);
Rate Limit
This endpoint allows 10 requests per 60-second window per organization, separate from the default limit.
Error Responses
- 422 Validation
- 400 Domain Unavailable
- 400 Domain Limit Reached
- 409 Conflict
- 401 Unauthorized
- 429 Rate Limited
{
"status_code": 422,
"name": "validation_error",
"message": "name must be a valid domain name"
}
The domain is registered by another organization, or a parent or subdomain of it is verified by another organization.
{
"status_code": 400,
"name": "domain_unavailable",
"message": "The domain 'mail.acme.com' is already registered by another organization. Please contact support if you believe this is an error."
}
{
"status_code": 400,
"name": "domain_limit_reached",
"message": "Your plan allows a maximum of 1 domain. Please upgrade your plan to add more domains or remove an existing domain."
}
{
"status_code": 409,
"name": "conflict",
"message": "Another domain is being registered for this organization. Try again in a moment."
}
{
"status_code": 401,
"name": "unauthorized",
"message": "Invalid API Key"
}
{
"status_code": 429,
"name": "rate_limit_exceeded",
"message": "Too many requests. Please retry after the period indicated in the retry-after header."
}