The AWS CLI v2 ships two SES namespaces side by side: aws ses, which targets the original SES Classic v1 API, and aws sesv2, which targets the modern SESv2 API and is the one AWS recommends for new work (AWS, 2026). The sesv2 namespace exposes roughly 130 subcommands, but in practice ten of them cover the vast majority of day-to-day SES operations: verifying domains, checking account state, sending test mail, managing the suppression list, and wiring up configuration sets. This tutorial walks through each, with copy-pasteable examples and the JSON shapes you actually need.
Prerequisites
This guide assumes AWS CLI v2 installed and configured with an IAM identity that has SES permissions. AWS CLI v1 is deprecated and does not include the full sesv2 surface; install the v2 package from the official AWS installer or your package manager (AWS, 2026). Confirm with aws --version; you should see aws-cli/2.x. The sesv2 namespace ships in every modern v2 release, no plugin required.
Authentication and permissions are the second prerequisite. Configure credentials with aws configure or environment variables, and make sure the IAM identity in use has at least ses:SendEmail, ses:GetAccount, and the identity-management actions you need. For a production-ready policy that covers verification, sending, and suppression management without granting blanket access, see our Amazon SES IAM permissions guide. For the broader account setup - root account, region choice, sandbox exit - see how to set up Amazon SES.
One operational note: every sesv2 command takes --region and --profile like any other AWS CLI call. SES is region-scoped, so a domain verified in us-east-1 is not automatically verified in eu-west-1. Set AWS_REGION in your shell or pass --region explicitly on every call; mixing regions silently is the most common source of "why is this domain not verified" debugging time.
Verifying a Domain Identity
Identity verification is the entry point for any SES setup, and the sesv2 namespace handles it in two calls. The first creates the identity and asks AWS to generate the verification material (DKIM CNAMEs for Easy DKIM, or a TXT challenge for domain verification); the second reads back the current status so you can confirm DNS propagation before sending. Both calls are idempotent within the same region.
To create a domain identity with Easy DKIM enabled:
aws sesv2 create-email-identity \
--email-identity example.com
The response includes a DkimAttributes object with three Tokens - these are the labels for the three CNAME records AWS expects you to publish at your DNS provider. The records take the form <token>._domainkey.example.com CNAME <token>.dkim.amazonses.com. Publish all three, then poll the verification status:
aws sesv2 get-email-identity \
--email-identity example.com
Look for VerifiedForSendingStatus: true and DkimAttributes.Status: SUCCESS. If either is PENDING after 24 hours, the DNS records have not propagated; recheck them with dig +short CNAME <token>._domainkey.example.com. The same get-email-identity call also returns the MAIL FROM configuration, feedback forwarding setting, and configuration set association, so it doubles as a "what does SES think about this domain" diagnostic.
For a single email address rather than a full domain, the same create-email-identity command works - pass an email address instead of a domain and SES sends a confirmation link to that address. Useful for sandbox testing, less useful in production where you want domain-level signing.
Checking Your Account State
The get-account call is the single most useful sesv2 command for daily operational checks. It returns six fields that together describe everything you usually need to know about an SES account without opening the console: sandbox status, daily quota, send-rate cap, suppression configuration, dedicated-IP warmup status, and enforcement status (AWS, 2026).
aws sesv2 get-account
A trimmed sample response:
{
"DedicatedIpAutoWarmupEnabled": true,
"EnforcementStatus": "HEALTHY",
"ProductionAccessEnabled": true,
"SendQuota": {
"Max24HourSend": 50000.0,
"MaxSendRate": 14.0,
"SentLast24Hours": 12483.0
},
"SendingEnabled": true,
"SuppressionAttributes": {
"SuppressedReasons": ["BOUNCE", "COMPLAINT"]
}
}
The fields to scan first: ProductionAccessEnabled: false means you are still in the sandbox and can only send to verified recipients. EnforcementStatus of anything other than HEALTHY (PROBATION or SHUTDOWN) means AWS has flagged the account; respond to the linked Support case before sending another batch. SendingEnabled: false is a paused account. The SendQuota block is your hard ceiling - cross Max24HourSend in a rolling 24-hour window and SES will start rejecting calls.
This one command replaces a dozen console clicks. Wire it into your deployment pipeline as a pre-send health check and you will catch sandbox-trapped staging environments and quota exhaustion before they bite a live campaign.
Sending a Test Email
The send-email command takes a single message and returns a MessageId. The minimum required arguments are --from-email-address, --destination, and --content. Inline JSON works fine for one-offs, but anything more complex (HTML body, custom headers, configuration set) is much cleaner to pass via --cli-input-json and a file.
Inline form for a quick test:
aws sesv2 send-email \
--from-email-address "sender@example.com" \
--destination "ToAddresses=recipient@example.com" \
--content '{
"Simple": {
"Subject": {"Data": "SES CLI test"},
"Body": {"Text": {"Data": "Hello from aws sesv2 send-email."}}
}
}'
The same payload as a file, ready to drop into a script:
{
"FromEmailAddress": "sender@example.com",
"Destination": {
"ToAddresses": ["recipient@example.com"]
},
"Content": {
"Simple": {
"Subject": {"Data": "SES CLI test"},
"Body": {
"Text": {"Data": "Hello from aws sesv2 send-email."},
"Html": {"Data": "<p>Hello from <strong>aws sesv2 send-email</strong>.</p>"}
}
}
},
"ConfigurationSetName": "transactional"
}
Then call:
aws sesv2 send-email --cli-input-json file://send-test.json
A successful response is a one-line {"MessageId": "0102..."} payload. From a sandbox account, the recipient address must already be a verified identity - otherwise SES returns MessageRejected: Email address is not verified. Production accounts can send to any address that is not on the account-level suppression list.
For templated, bulk, or raw MIME sends, the sister commands are send-bulk-email (one template, many destinations with per-recipient variables) and send-email --content '{"Raw": {...}}' (you provide the full MIME blob). The vast majority of CLI sends are simple message form like the example above.
Managing the Suppression List
The account-level suppression list is where SES parks addresses that have hard-bounced or complained, so future sends to them silently drop instead of damaging your reputation. The sesv2 namespace exposes the full lifecycle: list, add, remove, and configure which event types auto-populate the list.
To see what is currently suppressed:
aws sesv2 list-suppressed-destinations \
--reasons BOUNCE COMPLAINT \
--page-size 100
The response paginates via NextToken. To add an address manually - for example, a customer who unsubscribed via your own UI and you want SES to also refuse future sends:
aws sesv2 put-suppressed-destination \
--email-address "unsubscribed@example.com" \
--reason COMPLAINT
To remove an address (typically after manual verification that the bounce was a transient mailbox issue):
aws sesv2 delete-suppressed-destination \
--email-address "recovered@example.com"
The account-level auto-suppression behaviour is controlled by put-account-suppression-attributes. To have SES auto-suppress on both bounces and complaints:
aws sesv2 put-account-suppression-attributes \
--suppressed-reasons BOUNCE COMPLAINT
To disable auto-suppression entirely (you take full responsibility for filtering bad addresses out of every send):
aws sesv2 put-account-suppression-attributes \
--suppressed-reasons
Most production accounts run with both reasons enabled; the operational cost of an extra layer of filtering is zero, and the reputation cost of resending to known-bad addresses is high.
Configuration Sets, Identity Policies, and Feedback
Configuration sets are SES's per-send policy container - which event destinations to publish to, which IP pool to use, whether to override reputation tracking. Three CLI calls cover the common setup: create the set, attach an event destination, then reference the set name in send-email.
aws sesv2 create-configuration-set \
--configuration-set-name "transactional" \
--reputation-options ReputationMetricsEnabled=true \
--sending-options SendingEnabled=true
Attach a CloudWatch event destination so per-message events (send, delivery, bounce, complaint, open, click) flow into CloudWatch metrics for alarming:
aws sesv2 create-configuration-set-event-destination \
--configuration-set-name "transactional" \
--event-destination-name "cloudwatch" \
--event-destination '{
"Enabled": true,
"MatchingEventTypes": ["SEND", "DELIVERY", "BOUNCE", "COMPLAINT"],
"CloudWatchDestination": {
"DimensionConfigurations": [
{
"DimensionName": "MessageTag",
"DimensionValueSource": "MESSAGE_TAG",
"DefaultDimensionValue": "default"
}
]
}
}'
For SNS or Kinesis Firehose destinations instead of CloudWatch, swap the inner key (SnsDestination, KinesisFirehoseDestination) and supply the topic or stream ARN. The full lifecycle - update, list, delete event destinations - has matching update-, list-, and delete- commands in the same shape. For the underlying event-publishing model and which destination to pick for which monitoring use case, see our SES event publishing guide.
The sesv2 command surface for the rest of the operational map - identity policies, MAIL FROM, custom verification email templates, dedicated IP pools - follows the same pattern: a create-, get-, put-, update-, delete-, and list- family per resource. Once you have used five or six of them, the rest are predictable.
SESv2 vs SES Classic Command Surface
Both namespaces ship in AWS CLI v2, and you can still run the older aws ses commands against the v1 API. AWS keeps v1 around for backward compatibility but recommends SESv2 for new projects (AWS, 2026). The headline reason is reach: v1 cannot manage the account-level suppression list, configuration set event destinations beyond a narrow subset, or the modern identity attributes. The chart below shows the rough split in command count.
If you have legacy scripts pinned to aws ses send-email or aws ses verify-domain-identity, they will keep working. New automation should target sesv2, and migrations are usually a sed-equivalent rename plus a few JSON shape adjustments (the v2 API uses nested objects where v1 used flat parameters).
Quick Command Reference
The table below condenses the ten commands that cover almost every day-to-day SES operation into a single lookup. Bookmark it and you will rarely need to dig through aws sesv2 help.
| Command | Purpose |
|---|---|
get-account |
Read sandbox status, daily quota, send-rate, enforcement status in one call. |
create-email-identity |
Verify a domain or single address; returns DKIM CNAME tokens to publish. |
get-email-identity |
Check verification status, DKIM mode, MAIL FROM config for an identity. |
send-email |
Send a single message; accepts inline JSON or --cli-input-json file. |
send-bulk-email |
One template, many destinations with per-recipient variables. |
list-suppressed-destinations |
List addresses on the account-level suppression list, optionally filtered. |
put-suppressed-destination |
Manually add an address to the suppression list. |
delete-suppressed-destination |
Remove an address from the suppression list. |
put-account-suppression-attributes |
Control which event types (BOUNCE, COMPLAINT) auto-populate the list. |
create-configuration-set |
Create a per-send policy container for event destinations and IP pools. |
The pattern is consistent: every resource (identity, configuration set, suppression entry, template, dedicated IP pool, contact list) has matching create-, get-, update-, delete-, and list- verbs. Once you have used the ten above, the rest of the 130-command surface is mostly a search through aws sesv2 help.
When You Would Want Infrastructure-as-Code Instead
The CLI is best for one-offs, exploration, and debugging. For anything you intend to apply repeatedly - a fresh region, a new staging account, a multi-domain rollout - hand-rolled CLI commands drift quickly. Terraform or CloudFormation declarative resources track desired state instead of imperatively setting it, and they refuse to silently re-create something that already exists. We cover the SES-specific Terraform resource shapes (aws_sesv2_email_identity, aws_sesv2_configuration_set, aws_sesv2_dedicated_ip_pool) in our Amazon SES Terraform setup guide.
The pragmatic split most teams settle on: Terraform owns identities, configuration sets, IAM roles, and SNS topics; the CLI is for inspecting state, debugging a verification stall, manually adjusting the suppression list, or sending a one-off test from a developer laptop. Mixing the two only causes problems when the CLI is used to mutate a resource Terraform manages - Terraform will revert the change on the next plan.
How Mailblast Handles the CLI Work for You
Mailblast operates on your own Amazon SES account (BYO-SES), and the work the CLI commands above describe is exactly the work Mailblast does for you under the hood. When you connect a domain, Mailblast calls create-email-identity against your SES account using the IAM role you grant it, surfaces the DKIM CNAMEs for you to publish, and polls get-email-identity until verification clears. When you launch a campaign, Mailblast calls send-email (or send-bulk-email) for each message, attaches the appropriate configuration set, and consumes the event stream you wired up.
That is the steady-state value. The CLI remains useful for the one-off cases Mailblast cannot reach: debugging why an identity is stuck PENDING, manually removing an address from the suppression list after a customer service interaction, or sending a probe message outside the campaign UI. Both tools talk to the same SES account through the same API - they are complementary, not exclusive. For the rest of the SES setup story before Mailblast enters the picture, the SES setup guide is the entry point.
FAQ
What is the difference between aws ses and aws sesv2?
Both namespaces ship in AWS CLI v2. aws ses targets the legacy SES Classic v1 API and is kept for backward compatibility. aws sesv2 targets the modern SESv2 API and is what AWS recommends for new projects (AWS, 2026). SESv2 has a wider command surface (130+ subcommands), including the account-level suppression list and configuration set event destinations that the v1 namespace cannot reach.
Do I need anything special installed to use the SES CLI?
Just AWS CLI v2. The sesv2 namespace ships with every modern AWS CLI v2 release - no plugin, no SDK install, no extra package (AWS, 2026). You also need an IAM identity with at least ses:SendEmail and ses:GetAccount permissions for sending, plus identity-management permissions for verification work.
How do I send a test email from the AWS CLI?
Use aws sesv2 send-email with --from-email-address, --destination, and --content. The simplest invocation is a JSON Simple message body inline; for production scripting, pass --cli-input-json with a file. Sandbox accounts can only send to verified addresses, so verify the recipient with create-email-identity first.
Can I check whether my SES account is still in the sandbox from the CLI?
Yes. Run aws sesv2 get-account. The response includes ProductionAccessEnabled (false in sandbox, true after AWS grants production access), SendQuota with Max24HourSend and MaxSendRate, and EnforcementStatus showing HEALTHY, PROBATION, or SHUTDOWN. This single call replaces a dozen console clicks for daily ops checks.
How do I manage the SES suppression list from the CLI?
Three sesv2 commands: list-suppressed-destinations to see what is on the list, put-suppressed-destination to add an address, delete-suppressed-destination to remove one. Combine with put-account-suppression-attributes to control whether bounces, complaints, or both auto-populate the list at the account level.
Disclosure: Mailblast is a hosted management layer for your own Amazon SES account. The CLI commands described here run against your AWS account directly; Mailblast uses the same SESv2 API surface via the AWS SDK to wire up identities, configuration sets, and sending on your behalf.