Telnyx Email Domains — curl

SkillCommunication

Manage email sending domains, verify DNS records (SPF, DKIM, DMARC, MX), check domain health, and configure domain-level webhooks for delivery events.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Telnyx Email Domains — curl skill

What this skill tells your AI

The instructions your AI receives, as published by team-telnyx/ai in skills/telnyx-email-domains-curl/SKILL.md and read by ahel’s review.

Installation

# curl is pre-installed on macOS, Linux, and Windows 10+.
# A JSON formatter such as `python3 -m json.tool` is optional.

Setup

export TELNYX_API_KEY="YOUR_API_KEY_HERE"
export TELNYX_API_BASE="https://api.telnyx.com/v2"

# Set these from API responses after creating or listing resources.
export EMAIL_DOMAIN_ID="123e4567-e89b-12d3-a456-426614174000"
export EMAIL_WEBHOOK_ID="123e4567-e89b-12d3-a456-426614174003"

Every request requires:

-H "Authorization: Bearer $TELNYX_API_KEY"

Mutation requests with JSON also require:

-H "Content-Type: application/json"

Use --fail-with-body --silent --show-error in automation so non-2xx responses fail the command without hiding the Telnyx error body.

Error Handling

Error responses use an errors array:

{
  "errors": [
    {
      "code": "10015",
      "title": "Validation Failed",
      "detail": "domain is invalid",
      "source": {"pointer": "/data/attributes/domain"}
    }
  ]
}

Common cases:

HTTPMeaningAction
400Invalid list query or malformed inputFix the query; do not retry unchanged.
401Missing or invalid API keyFix authentication.
403Shared domain is read-only (10008) or access is insufficientUse an owned custom domain or correct permissions.
404Domain or webhook not found (10001)Re-list resources and verify both IDs.
422Request validation or state transition failed (10015 and related codes)Inspect every error and source.pointer; correct the request or state.
429Rate limitHonor Retry-After when present and back off.
500Unexpected service errorRetry only safe reads or carefully reconciled mutations.

Do not retry a create blindly after a transport timeout; first list domains and check whether the resource was created. verify and GET operations are safe to repeat. Before retrying DELETE or PATCH, retrieve the current state. Use bounded exponential backoff with jitter for transient 429 and 5xx failures.

Important Notes

  • All 13 reachable operations use the Telnyx v2 REST API and Bearer authentication.
  • A custom domain is not ready merely because POST /v2/email_domains succeeds. Create it, retrieve its generated DNS records, publish those records, trigger verification, and check health until usable_for_sending is true.
  • Call GET /v2/email_domains/{domain_id}/dns_records to retrieve the exact DNS records you need to publish. The response includes the record type, host, value, and priority for each record.
  • The OpenAPI DNS-purpose enum includes ownership, spf, dkim, dmarc, and mx. SPF, DKIM, and DMARC are authentication-related purposes; MX supports inbound routing when required. Publish the exact API-returned values rather than constructing DNS records from examples.
  • Webhooks are configured at the domain level through POST /v2/email_domains/{domain_id}/webhooks, not per message.
  • Domain IDs and webhook IDs are UUIDs returned by the API, not domain names.

Operational Caveats

  • Shared versus custom domains: Telnyx-managed shared domains are pre-provisioned and readable/usable by accounts. Custom domains require customer DNS setup and verification. Non-owners cannot update, verify, or delete a shared domain; those attempts return 403 with code 10008.
  • DNS is API-generated: The API does not expose customer-facing create/update/delete operations for individual generated DNS records. Publish records at the authoritative DNS provider, then call the verify operation.
  • Tracking defaults live on the domain: open_tracking, click_tracking, and unsubscribe_tracking default to false, false, and true, respectively. A send may override these defaults without changing the domain.
  • Health is the readiness signal: Do not infer deliverability from one DNS record. Check the aggregate health response and the relevant usability boolean.
  • Verification reflects DNS propagation: A successful verify request means the check ran, not that every record passed. Wait and use bounded backoff before checking again; never tight-loop verification.
  • Verified deletion requires intent: Pass force=true to delete a verified custom domain. Delete returns 200 with the deleted domain, not 204.
  • Pagination differs by resource: Domain lists support offset or cursor pagination. Webhook lists support offset pagination only. Treat cursors as opaque and inspect the returned .meta shape.

Reference Use Rules

Do not invent request fields, DNS values, event names, response fields, or status enums.

Core Tasks

Provision and verify a custom domain

1. Create a domain

POST /v2/email_domains

ParameterTypeRequiredDescription
domainstringYesCustom domain name, for example example.com.
inbound_enabledbooleanNoEnable inbound routing; defaults to false.
dmarc_policyobject | nullNoAdvisory DMARC policy (p, pct, rua, sp).
trackingobjectNoDomain defaults for open, click, and unsubscribe tracking.
curl --fail-with-body --silent --show-error \
  -X POST \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "inbound_enabled": true,
    "dmarc_policy": {
      "p": "none",
      "pct": 100,
      "rua": "mailto:dmarc@example.com"
    },
    "tracking": {
      "open_tracking": true,
      "click_tracking": true,
      "unsubscribe_tracking": true
    }
  }' \
  "$TELNYX_API_BASE/email_domains"

Expected status: 201. Save .data.id as EMAIL_DOMAIN_ID. Do not send until .data.usable_for_sending is true.

2. Retrieve the required DNS records

GET /v2/email_domains/{domain_id}/dns_records

ParameterTypeRequiredDescription
domain_idUUID path parameterYesDomain ID returned by the API.
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/dns_records"

Each item in .data[] includes purpose, record_type, host, value, priority, required, status, and possibly actual_value. Publish every required record exactly as returned. Use the response to decide which records are required for this domain's sending and inbound configuration.

3. Trigger DNS verification

POST /v2/email_domains/{domain_id}/verify

ParameterTypeRequiredDescription
domain_idUUID path parameterYesDomain whose current DNS records should be checked.
Request bodyNoThis operation has no request body.
curl --fail-with-body --silent --show-error \
  -X POST \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/verify"

Expected status: 200. Inspect .data.verification and each .data.dns_records[].status. A 200 means the check ran; it does not guarantee that every record verified.

4. Check domain health

GET /v2/email_domains/{id}/health

ParameterTypeRequiredDescription
idUUID path parameterYesDomain whose aggregate readiness should be checked.
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/health"

Read .data.status, .data.usable_for_sending, .data.usable_for_inbound, .data.verification, and .data.checked_at. DMARC may be missing_optional without blocking sending; use each record's required flag and the health booleans rather than treating every non-verified value as fatal.

List domains

GET /v2/email_domains

Query parameterTypeRequiredDescription
page[number]integerNoOffset page number.
page[size]integerNoPage size from 1 to 100.
sortenumNocreated_at, -created_at, domain, or -domain.
filter[type]enumNocustom, shared, or shared_inbound.
filter[usable_for_sending]booleanNoLimit results by sending readiness.
...See all list query parameters.
curl --fail-with-body --silent --show-error --get \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  --data-urlencode "page[number]=1" \
  --data-urlencode "page[size]=25" \
  --data-urlencode "sort=-created_at" \
  --data-urlencode "filter[type]=custom" \
  --data-urlencode "filter[usable_for_sending]=true" \
  "$TELNYX_API_BASE/email_domains"

Supported filters also include status, partial case-insensitive domain, profile_id, and usable_for_inbound. Domain lists support offset pagination and cursor pagination; inspect the returned .meta shape.

Retrieve a domain

GET /v2/email_domains/{id}

ParameterTypeRequiredDescription
idUUID path parameterYesDomain to retrieve.
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID"

Expected status: 200. The response includes DNS, DKIM, inbound, DMARC, tracking, usability, timestamps, and optional reputation information.

Update a domain

PATCH /v2/email_domains/{id}

ParameterTypeRequiredDescription
idUUID path parameterYesDomain to update.
inbound_enabledbooleanNoEnable or disable inbound routing.
dmarc_policyobject | nullNoChange the advisory DMARC policy.
trackingobjectNoChange domain tracking defaults.

The domain name and type are not mutable. Include at least one field to change. Updating the DMARC policy rebuilds the recommended DMARC record and resets its verification to pending, so retrieve the new DNS records, publish the returned value, and verify again.

curl --fail-with-body --silent --show-error \
  -X PATCH \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inbound_enabled": true,
    "tracking": {
      "open_tracking": false,
      "click_tracking": true,
      "unsubscribe_tracking": true
    }
  }' \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID"

Expected status: 200. A non-owner cannot mutate a shared domain (403, code 10008).

Delete a domain

DELETE /v2/email_domains/{id}

ParameterTypeRequiredDescription
idUUID path parameterYesDomain to delete.
forceboolean query parameterFor verified domainsMust be true to delete a verified domain.
# For a pending or unverified custom domain:
curl --fail-with-body --silent --show-error \
  -X DELETE \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID"

# For a verified custom domain, explicitly confirm deletion:
curl --fail-with-body --silent --show-error --get \
  -X DELETE \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  --data-urlencode "force=true" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID"

Expected status: 200 with the deleted domain in .data. A non-owner cannot delete a shared domain.

Webhooks

Webhooks are configured at the domain level through POST /v2/email_domains/{domain_id}/webhooks, not per message. A subscription contains a delivery URL and a non-empty explicit event allowlist.

Verify and process webhook deliveries

Telnyx signs webhook deliveries with Ed25519 and sends the telnyx-signature-ed25519 and telnyx-timestamp headers. Follow this order for every delivery:

  1. Read and retain the request's raw body bytes. Do not parse JSON first; changing whitespace or serialization before verification invalidates the signed body.
  2. Read telnyx-timestamp and reject requests outside a 5-minute timestamp tolerance to limit replay attacks.
  3. Verify telnyx-signature-ed25519 against the timestamp and raw body with your Telnyx Ed25519 public key. Use the official Telnyx verifier for your runtime where available. Reject the request before parsing or processing if signature verification fails.
  4. Parse the verified body, extract its event ID, and atomically record that ID. If the event ID was already processed, return a success response without repeating side effects.
  5. Persist or enqueue work, then return a 2xx response within 10 seconds. Keep slow downstream processing outside the request path.

Telnyx retries on timeout or non-2xx. Keep your endpoint idempotent.

Webhook events

The current OpenAPI EmailWebhookEvent enum contains these exact subscribable event types:

CategoryEvent types
Outbound lifecycleemail.scheduled, email.sandbox, email.queued, email.sending, email.sent, email.delivered, email.deferred, email.bounced, email.failed
Engagementemail.complained, email.opened, email.clicked, email.unsubscribed
Inboundemail.received
Domain lifecycleemail_domain.created, email_domain.verified, email_domain.degraded, email_domain.suspended, email_domain.deleted

Use exact case and punctuation. The create request requires at least one event; there is no implicit all-events subscription. PATCH replaces the event list, so include the complete desired allowlist.

List webhooks

GET /v2/email_domains/{domain_id}/webhooks

Query parameterTypeRequiredDescription
domain_idUUID path parameterYesParent domain.
page[number]integerNoOffset page number; defaults to 1.
page[size]integerNoPage size from 1 to 100; defaults to 25.
sortenumNocreated_at or -created_at.
curl --fail-with-body --silent --show-error --get \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  --data-urlencode "page[number]=1" \
  --data-urlencode "page[size]=25" \
  --data-urlencode "sort=-created_at" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/webhooks"

Expected status: 200 with .data[] and .meta. Webhook lists use offset pagination only.

Create a webhook

POST /v2/email_domains/{domain_id}/webhooks

ParameterTypeRequiredDescription
domain_idUUID path parameterYesParent domain.
urlURI stringYesWebhook delivery destination.
eventsarray of EmailWebhookEventYesNon-empty exact event allowlist.
curl --fail-with-body --silent --show-error \
  -X POST \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/email",
    "events": [
      "email.queued",
      "email.sent",
      "email.delivered",
      "email.bounced",
      "email.failed",
      "email.received",
      "email_domain.verified"
    ]
  }' \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/webhooks"

Expected status: 201. Save .data.id as EMAIL_WEBHOOK_ID.

Retrieve a webhook

GET /v2/email_domains/{domain_id}/webhooks/{id}

ParameterTypeRequiredDescription
domain_idUUID path parameterYesParent domain.
idUUID path parameterYesWebhook to retrieve.
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/webhooks/$EMAIL_WEBHOOK_ID"

Expected status: 200. Confirm .data.domain_id matches the domain in the path.

Update a webhook

PATCH /v2/email_domains/{domain_id}/webhooks/{id}

ParameterTypeRequiredDescription
domain_idUUID path parameterYesParent domain.
idUUID path parameterYesWebhook to update.
urlURI stringNoNew delivery destination.
eventsarray of EmailWebhookEventNoReplacement non-empty event allowlist.

The request may update url, events, or both. domain_id is bound at creation and cannot be changed.

curl --fail-with-body --silent --show-error \
  -X PATCH \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      "email.sent",
      "email.delivered",
      "email.bounced",
      "email.complained",
      "email.opened",
      "email.clicked",
      "email.unsubscribed"
    ]
  }' \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/webhooks/$EMAIL_WEBHOOK_ID"

Expected status: 200. Verify the returned .data.events contains the complete desired allowlist.

Delete a webhook

DELETE /v2/email_domains/{domain_id}/webhooks/{id}

ParameterTypeRequiredDescription
domain_idUUID path parameterYesParent domain.
idUUID path parameterYesWebhook to delete.
curl --fail-with-body --silent --show-error \
  -X DELETE \
  -H "Authorization: Bearer $TELNYX_API_KEY" \
  "$TELNYX_API_BASE/email_domains/$EMAIL_DOMAIN_ID/webhooks/$EMAIL_WEBHOOK_ID"

Expected status: 200 with the deleted webhook in .data, not 204.

Additional Operations

All 13 reachable operations are indexed below. Use the inline core tasks first; for exhaustive optional parameters and response schemas, read references/api-details.md.

#OperationOperation IDEndpointRequired params
1List domainslistEmailDomainsGET /v2/email_domainsNone
2Create a domaincreateEmailDomainPOST /v2/email_domainsdomain
3Retrieve a domaingetEmailDomainGET /v2/email_domains/{id}id
4Update a domainupdateEmailDomainPATCH /v2/email_domains/{id}id; include at least one update field
5Delete a domaindeleteEmailDomainDELETE /v2/email_domains/{id}id; force=true for a verified domain
6Get domain healthgetEmailDomainHealthGET /v2/email_domains/{id}/healthid
7List generated DNS recordslistEmailDomainDnsRecordsGET /v2/email_domains/{domain_id}/dns_recordsdomain_id
8Verify current DNSverifyEmailDomainDnsRecordsPOST /v2/email_domains/{domain_id}/verifydomain_id
9List domain webhookslistEmailDomainWebhooksGET /v2/email_domains/{domain_id}/webhooksdomain_id
10Create a domain webhookcreateEmailDomainWebhookPOST /v2/email_domains/{domain_id}/webhooksdomain_id, url, events
11Retrieve a domain webhookgetEmailDomainWebhookGET /v2/email_domains/{domain_id}/webhooks/{id}domain_id, id
12Update a domain webhookupdateEmailDomainWebhookPATCH /v2/email_domains/{domain_id}/webhooks/{id}domain_id, id; include url, events, or both
13Delete a domain webhookdeleteEmailDomainWebhookDELETE /v2/email_domains/{domain_id}/webhooks/{id}domain_id, id

Before using lower-frequency optional parameters or branching on response fields, read the list-query section, the request schemas, and the response schemas.

Signals

GitHub stars
214
Forks
21
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
telnyx-email-domains-curl
Source
github.com/team-telnyx/ai