Skip to content

Test an exact email service

Use this flow to request one diagnostic message through an application's applied AWS SES managed service, then inspect its content-free history. It does not test the installation's global alert channel or certify general mail delivery.

The release-matched offline guide is astro docs show email-delivery, with mail and email aliases. The operations below use the existing astro api graphql command; there is no dedicated test-send CLI verb.

Check support and authority

Run astro status --json and check for email.exact_service_delivery_tests, email.delivery_test_history and email.signed_delivery_observations. These markers describe the installed API. They grant no permission and do not prove that SES, DNS, feedback delivery or an individual service is ready.

Select the exact organization and managed-service GUID. The service must be active, have no unfinished operation, and retain an applied email / ses binding to its current app and environment. The server derives the original account, region, SES identity and sender from that binding and its current cluster credential declaration; callers cannot supply another provider or sender.

Sending and the support check require fresh app.update and managed_service.update authority on the exact service/app, including current membership, credential ceilings and policy checks. A bearer currently needs the admin scope ceiling for managed_service.update; write:apps alone is insufficient. This guide does not widen scopes or replace organization grants. History reads require current app.read authority. Account permission lists are informational; they do not establish current bearer or target authority.

Save this document as email-support.graphql:

query EmailTestSupport($service: GUID!) {
  emailDeliveryTestSupport(managedServiceId: $service) {
    allowed reason serviceVersion sender identity accountId region
  }
}
astro --server staging --org ORGANIZATION_GUID api graphql \
  --file email-support.graphql --var service=MANAGED_SERVICE_GUID

An allowed support response confirms the current applied-source metadata and write admission. It performs no native sending-identity, suppression or configuration-set check. Review its exact service version, sender, account, region and identity before preparing a message.

Use the domain or app entry

Open Domains, select the exact registered domain, then Domain email delivery (/domains/DOMAIN_GUID/email). Choose a currently authorized app and its exact email service/environment; search and page through the available choices rather than inferring a sender from the app's routed hostname. The current-source panel shows the service version, sender, identity, AWS account and region. The association notice compares the sender with the exact domain or a dot-separated subdomain; this name match proves neither DNS authentication nor permission to use another service.

Enter one agreed recipient and, optionally, a subject and message. Select Review recipient and source, then Send reviewed test. Editing the content or refreshing the current source requires another review. Read the returned outcome and Delivery test history; an accepted result is not delivery. The app's managed-service summary and email-service detail also use this same exact-service review and history flow.

Mail DNS checks runs separate bounded MX, apex TXT/SPF and DMARC TXT lookups. Supply the operator's explicit DKIM selector and choose DKIM record type TXT or CNAME to match the provider's declared setup. Do not guess the selector from an app name. The result identifies the hostname, record type, observation perspective and time. It neither changes DNS nor sends email.

On an uncertain reply, refresh history for the original request ID before another send. Reload recovery retains content-free request metadata, not the recipient, subject or message draft. Re-enter the original content to retry that exact request. A changed source or lost current authority invalidates the original review; SMTP, Azure email and alert-channel tests have no fallback here.

Inspect email DNS separately

Use the exact registered domain GUID from the selected installation. Replace example.com with that zone, and DKIM_SELECTOR with the selector explicitly provided by your email operator or sending provider. Do not guess a selector from the app, sender or service name.

astro --server staging --org ORGANIZATION_GUID operator domains lookup DOMAIN_GUID \
  --hostname example.com --record-type MX --json
astro --server staging --org ORGANIZATION_GUID operator domains lookup DOMAIN_GUID \
  --hostname example.com --record-type TXT --json
astro --server staging --org ORGANIZATION_GUID operator domains lookup DOMAIN_GUID \
  --hostname _dmarc.example.com --record-type TXT --json
astro --server staging --org ORGANIZATION_GUID operator domains lookup DOMAIN_GUID \
  --hostname DKIM_SELECTOR._domainkey.example.com --record-type TXT --json

The apex TXT answer can contain SPF (v=spf1); the _dmarc TXT answer can contain DMARC (v=DMARC1). Inspect the returned values, not just command success. If the operator's exact DKIM setup specifies a CNAME, query that same selector with --record-type CNAME instead of treating a missing TXT answer as failure.

Each lookup reads the exact current domain version, then runs a bounded typed DNS probe through the server's fixed public resolver under current authority. Out-of-zone, stale or unavailable targets refuse; there is no local-resolver or alternate-server fallback. Inspect state, reason, perspective, checkedAt and values. NXDOMAIN, no answer and resolver failure are distinct outcomes. Private or split DNS can differ from this public observation.

MX or TXT record presence does not prove SPF evaluation, a DKIM signature, DMARC alignment, SES identity verification or recipient delivery. These commands read DNS only; they neither change records nor send a diagnostic message. See Domains and DNS for delegation and provider-record checks.

Request one reviewed message

Choose an agreed recipient and keep a new UUID as requestId. Keep that nonce and the original service, recipient, subject and body together in a private request file. Reusing the same nonce and identical intent recovers the retained test row; changing the service or content under that nonce is a conflict. A lost reply or unknown result is not permission to generate a new nonce and send again. Read history or recover the same request first.

Save this as email-send.graphql:

mutation SendEmailTest($input: SendEmailDeliveryTestInput!) {
  sendEmailDeliveryTest(input: $input) {
    ok
    errors { code message field }
    data {
      id managedServiceId version requestId sender recipient
      accountId region identity transport status providerMessageId
      eventTrackingConfigured simulator createdAt acceptedAt observedAt reasonCode
    }
  }
}

Save a private email-test.json, replacing the GUIDs, reviewed service version and recipient. Omitted subject and body use the server's diagnostic defaults. Do not put real content or addresses into shared shell history or repositories.

{
  "input": {
    "managedServiceId": "MANAGED_SERVICE_GUID",
    "expectedVersion": 12,
    "requestId": "ORIGINAL_REQUEST_UUID",
    "recipient": "agreed-recipient@example.com"
  }
}
astro --server staging --org ORGANIZATION_GUID api graphql \
  --file email-send.graphql --vars-file email-test.json

--vars-file supplies a JSON object; repeatable --var name=value flags override individual variables. The CLI reports top-level GraphQL errors as failures, but an HTTP 200 or CLI exit zero does not establish mutation success. Inspect sendEmailDeliveryTest.ok, its structured errors, and the returned row. A successful envelope can contain a failed, suppressed or uncertain test status.

The native send path verifies the current AWS account and tagged service-owned SES identity, its sending verification, recipient suppression and the configured feedback destination. Suppression is never bypassed, including simulator tests. The native path confirms the account through STS. The configured role must allow ses:GetEmailIdentity, ses:GetSuppressedDestination, ses:GetConfigurationSetEventDestinations and ses:SendEmail according to the actual installed IAM policy and resource bounds. The test sends at most one native message request; ambiguous acceptance is not retried. A received acceptance acknowledgement is retained before the final public fresh-authority check, so a refused reply can still require read-only recovery of the original row.

Native waits recheck current authority and the complete original binding; an edit or withdrawal can refuse further work. A committed intent does not prove that a request reached SES. Automatic retry of an uncertain send is disabled.

Subject is bounded to 200 UTF-8 bytes, with control characters refused; the text body is bounded to 8,192 UTF-8 bytes. The server persists an intent digest, sender/recipient, source metadata, status and provider message identity, not the subject or body. Those content fields are excluded from application audit, logging and tracing. Protect the local request file and the address-bearing history response as private data.

Read history and distinguish outcomes

Save this as email-history.graphql:

query EmailTestHistory($service: GUID!, $after: String, $limit: Int! = 25) {
  emailDeliveryTestsPage(managedServiceId: $service, after: $after, limit: $limit) {
    items {
      id requestId managedServiceId version status sender recipient
      accountId region identity transport providerMessageId
      eventTrackingConfigured simulator createdAt acceptedAt observedAt reasonCode
    }
    nextCursor
  }
}
astro --server staging --org ORGANIZATION_GUID api graphql \
  --file email-history.graphql --var service=MANAGED_SERVICE_GUID --var limit=25

Follow the returned opaque nextCursor with --var after=RETURNED_CURSOR, keeping the same organization and service. The default is 25 rows and the maximum is 50. The legacy emailDeliveryTests list is bounded, but cannot traverse the whole history; use emailDeliveryTestsPage for paging.

Status Meaning and next action
submitting Intent exists; no confirmed acceptance. Read again with the original identity.
accepted SES returned a message ID. This is provider acceptance, not recipient delivery.
unknown Acceptance is uncertain, or submitting has aged past one minute. Preserve the nonce; do not blindly resend.
suppressed The recipient is suppressed. Resolve the underlying condition through the appropriate operator process; the test does not remove suppression.
failed Preflight or send failed; inspect the bounded reason and current source before any separately reviewed new test.
deferred A signed matching provider event reports delivery delay.
delivered A signed matching delivery event was observed; it does not prove a person read the message.
bounced, complained, rejected Matching provider feedback reports the indicated result.
observation_timed_out Accepted or deferred has aged past 15 minutes without a terminal observation. Delivery remains unconfirmed.

eventTrackingConfigured means the accepted send observed an enabled matching SNS destination, not proof that feedback will arrive. Test sends do not create or repair identities, configuration sets or event destinations. An older missing configuration set requires normal reviewed service revalidation/provisioning. Operators must configure SES_EVENTS_SNS_TOPIC_ARN for the same account/region, the matching configuration set and the installation's signed SNS callback path. Events must match the original test, service, account, sender, sole recipient, topic and message ID. Late matching events can update a timed-out display; duplicate events do not create another send. History reads compute timeout states without proving a remote failure or mutating a send into success.

Installation and remaining limits

Resolve public delegation and SES identity verification separately; see Domains and DNS. A DNS check does not prove sender verification or recipient delivery. An active managed-service status is a prerequisite, not a delivery certificate. Native calls use fixed AWS service endpoints, finite timeouts and a 30-second test-send budget. The current rate admission is three tests per actor, ten per service and thirty per organization per minute; its effective scope depends on the installed cache backend.

For the separate configured install SMTP channel, use the install alert SMTP guide. It tests only the operator's own mailbox and does not fall back from this app-owned service.

This flow currently supports the exact AWS SES app-bound service. Azure email, SMTP, global alert-channel tests and a fallback global mail transport are not implemented here. Microsoft 365, Google mail and shared inbox management are separate future work. Local proofs use controlled SDK/TLS transports and send no external email; installation configuration and an explicitly authorized real recipient test remain release/runtime acceptance work.

See capabilities, shared services and the API reference for discovery, binding and transport.