Generated banner for AWS Sms Voice
Generated logo for AWS Sms Voice

AWS Sms Voice

AWSOfficialPublished by AWS

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Low risk

From the author

Onboards RCS Business Messaging and Notify OTP via the pinpoint-sms-voice-v2 AWS CLI. Covers creating a branded RCS test agent (logo, banner, rich cards, suggested replies), adding verified testers, and sending/receiving the first RCS message; and configuring AWS End User Messaging Notify to send one-time-passcode (OTP) verification codes through AWS-managed phone numbers without buying a number or completing carrier registration. Also discovers which registration type a country requires from live API definitions and runs the universal registration lifecycle (create, derive fields, fill, submit, recover from denial, resubmit) shared by 10DLC, toll-free, short code, sender ID, RCS country launch, and Notify tier upgrade. Applicable to RCS agent setup, RCS message send/receive, Notify OTP configuration, verification-code delivery, registration requirements by country, and registration submission. Does not cover WhatsApp (see aws-social-messaging).
— the description by AWS

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to using the AWS End User Messaging SMS and Voice (pinpoint-sms-voice-v2) API. It guides users through the rapid setup of RCS Business Messaging test agents and Notify OTP configurations, as well as the more complex lifecycle of production registration for various messaging identities. It emphasizes using the AWS MCP server for secure, audited execution and provides clear paths for both testing and moving to production-grade messaging.

What it does

  • Creates branded RCS test agents with rich cards and suggested replies.
  • Configures Notify for OTP delivery using AWS-managed templates.
  • Discovers registration requirements for specific countries and identity types.
  • Executes the full registration lifecycle including submission and denial recovery.
  • Provides procedures for exiting the SMS sandbox and upgrading to production tiers.
  • Offers guidance on least-privilege IAM policies and security best practices.

When to use it

  • When you need to send rich, branded RCS messages to test devices.
  • When you require a fast, production-ready OTP verification solution.
  • When you need to determine registration requirements for international messaging.
  • When you are ready to move messaging identities from sandbox to production.
  • When you need to manage the registration lifecycle for 10DLC, toll-free, or short codes.

Advantages

  • Enables rapid prototyping with no phone number purchase required.
  • Provides a unified engine for diverse registration types.
  • Includes built-in failure mode analysis and cleanup procedures.
  • Promotes secure practices through least-privilege IAM and audit logging.
  • Supports both CLI and MCP-based execution for flexibility.

Usage examples

“Help me set up an RCS test agent to send a branded message.”
The agent will guide you through creating an RCS agent, adding a verified test device, and sending your first rich message using the pinpoint-sms-voice-v2 API.
“How do I send an OTP code using AWS Notify?”
The agent will assist in creating a Notify configuration, selecting an approved template, and sending a verification code to your specified phone number.
“What are the registration requirements for sending SMS in France?”
The agent will query live API definitions to identify the specific registration type and field requirements for the target country.
“I need to move my SMS identity out of the sandbox.”
The agent will walk you through the production go-to-production guide, including checking account attributes and managing spend limits.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/aws/aws-sms-voice/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip aws-sms-voice.zip -d .agents/skills

Documentation by AWS

AWS End User Messaging — SMS, Voice, RCS & Notify

Overview

AWS End User Messaging SMS and Voice (the pinpoint-sms-voice-v2 API) is the control and data plane for SMS, Voice, RCS Business Messaging, and Notify (OTP). This skill covers the two fastest onboarding paths — each reaches a real message on a real phone in about five minutes, with no phone-number purchase and no carrier registration:

  1. Create an RCS test agent and send your first branded message — a branded RCS Business Messaging agent, a verified test device, and a rich outbound + inbound message. See references/hello-rcs-test-agent.md.

  2. Send an OTP with Notify — a one-time-passcode text through AWS-managed sending identities and pre-approved templates. See references/hello-notify-otp.md.

Beyond onboarding, this skill takes an identity to production registration in three steps:

  1. Find which registration a country requires — discover the registration type and its live field set for a target country and identity type, reconciled with the official docs. See references/registration-requirements-by-country.md.

  2. Run the registration lifecycle — the universal engine (create, derive fields, fill, submit, poll, recover from denial, resubmit) shared by every registration type, including RCS country launch. See references/registration-core-engine.md.

  3. Take an identity to production — exit the SMS sandbox, launch RCS in a country with carrier monitoring and SMS fallback, upgrade Notify to Advanced, and apply production hygiene (keywords, configuration sets, protect configurations). See references/customer-go-to-production-guide.md.

Guardrail — where this skill's files live (MCP vs local install)

This skill can be loaded two ways, and the references/ files resolve differently:

  • Loaded through the AWS MCP retrieve_skill tool. Reference files do not exist on the local filesystem. Fetch them via retrieve_skill with the file parameter (e.g., file="references/hello-rcs-test-agent.md"). Do NOT file_read these paths locally — they are not there.

  • Installed locally (e.g., .kiro/skills/aws-sms-voice/). Read references from the local skill directory using the relative paths shown in this file.

This applies only to the skill's own packaged files. User-created artifacts (brand-assets/, temp files) are always in the user's working directory.

Execute commands using available tools from the AWS MCP server when connected — it provides sandboxed execution, audit logging, and observability. When the MCP server is not available, fall back to the AWS CLI or shell as needed.

Choosing a path

You want to…Go to
Send rich, branded messages (cards, logo, two-way) to your own test phonehello-rcs-test-agent.md
Send a verification code / OTP to any supported phone, fastest possible pathhello-notify-otp.md
Find out which registration a country needs and what fields it requiresregistration-requirements-by-country.md
Create and submit a registration of any type (10DLC, toll-free, sender ID, RCS launch, Notify tier)registration-core-engine.md
Take an identity to production (sandbox exit, RCS carrier launch + fallback, Notify Advanced, hygiene)customer-go-to-production-guide.md

RCS test agents deliver only to verified test devices and require a short brand registration (auto-reviewed in minutes). Notify sends to any recipient in its supported countries immediately, but is limited to AWS-managed OTP templates. Pick RCS to explore rich messaging; pick Notify for the shortest path to a production-grade verification text.

Before you start (both paths)

The AWS MCP server is recommended for the best experience here — it runs the many pinpoint-sms-voice-v2 and sts calls with sandboxed execution, audit logging, and observability. It is not a hard requirement: every step also works with the plain AWS CLI or the AWS Agent Toolkit when the MCP server is not available.

  1. Credentials. Run aws sts get-caller-identity --region <REGION> (pass --region to use the regional STS endpoint; the global sts.amazonaws.com endpoint is legacy). If it fails, configure the user's auth (named profile / SSO / access keys — --profile <PROFILE> on every command if a profile is in play) and prefer an assumed IAM role with ephemeral credentials. Each reference file's Prerequisites carry the full auth, CloudTrail-audit, and CLI-freshness steps.

  2. Least-privilege access. These tasks need a small set of specific sms-voice: actions in the target account (the reference files enumerate the read/write actions each step uses — for example sms-voice:DescribeRcsAgents, CreateRcsAgent, CreateRegistration, CreateNotifyConfiguration, and SendNotifyTextMessage). Scope the policy to exactly the actions the chosen path uses; do not grant sms-voice:* or *FullAccess.

  3. Don't trust the CLI version. Treat any Invalid choice '<operation>' on a pinpoint-sms-voice-v2 command, at any step, as a stale/partial local service model (not a permissions or account problem). The RCS and Notify reference files (prerequisite 3) carry the full reactive resolution: clear a stale ~/.aws/models/pinpoint-sms-voice-v2/ override, else upgrade the AWS CLI, with a boto3 fallback. AccessDeniedException means the identity is missing the required sms-voice: permissions.

Each reference file is self-contained: it tracks the IDs you collect as session state, audits required inputs against the service before any submit, and ends with a Failure modes table and a Cleanup section. Follow the file for the path the user chose rather than improvising the API sequence — several steps fail silently or depend on service-version-specific field sets that must be read live.

Security Considerations

  • Least-privilege IAM. Scope policies to specific sms-voice: actions; never use *FullAccess or sms-voice:* in production. The reference files enumerate the minimal read/write actions each onboarding path needs.

  • Ephemeral credentials. Assume an IAM role with ephemeral credentials; never embed long-lived access keys in code, config, or environment variables.

  • No secrets in messages or fields. OTP codes and message bodies can surface in CloudTrail and downstream logs — treat them as sensitive and never hardcode real codes. Enable CloudTrail logging for sms-voice API calls and encrypt CloudTrail logs and CloudWatch Log groups with a KMS CMK. Attach a configuration set with an event destination (CloudWatch Logs or Kinesis) for delivery monitoring before production traffic. Create CloudWatch alarms on key security signals (repeated AccessDeniedException, unusual send volumes, spend limit approaching threshold) for proactive alerting. Registration contact fields (email, phone, URLs) are submitted to partner review; use real but non-sensitive business contacts.

  • Consent and opt-out. Only message recipients who have opted in. Honor STOP / opt-out state; the RCS path shows how to check the opt-out list before sending. Test agents intentionally restrict delivery to verified test devices — do not work around that to reach unconsented numbers.

  • Spend limits and abuse. Default account spend limits are intentionally low; raise them deliberately and monitor. Notify enforces a non-adjustable per-day cap per destination — do not attempt to bypass it.

  • Destructive cleanup. The teardown steps delete agents, registrations, and configurations. They are irreversible — confirm with the user before running cleanup, and note RCS agents have deletion protection enabled by default in the onboarding flow.

Additional Resources

Files

references/hello-rcs-test-agent.md

Hello AWS End User Messaging: Create an RCS test agent and send your first message

By the end of this task, a branded RCS agent exists in the user's AWS account, their phone is a verified test device, and they have received a rich, branded message from the agent — plus sent one back. About 5 minutes of working time; the tester invitation adds a 2–20 minute wait that happens in the background. No carrier registration, no phone number purchase, no infrastructure. This unlocks the entire SMS and RCS control and data plane for everything you build next.

You run every command yourself. The user provides inputs and confirms what they see on their phone. Stop and consult the Failure modes table on any error.

Session state

Track these values as you collect them. They carry across all steps.

ValueFormatSource
REGIONe.g. us-east-1ask the user (default us-east-1)
PROFILEAWS CLI profile namecredential bootstrap (may be empty)
BRAND_NAME2–65 charsask the user
AGENT_IDrcs- + 32 hexCreateRcsAgent response
REG_IDregistration-...CreateRegistration response
LOGO_ID, BANNER_IDattachment-... (ID prefix is attachment- even though the parameter is --registration-attachment-id)CreateRegistrationAttachment responses
PHONEE.164, e.g. +12065550123ask the user (their real test device)
VDN_IDvdn- + 32 hexCreateVerifiedDestinationNumber response (needed for cleanup)

Prerequisites

  1. AWS CLI capability — probe, don't trust the version. A current-looking CLI can bundle a stale or partial service model that predates some pinpoint-sms-voice-v2 operations (observed live on CLI v2.33.15: Invalid choice 'create-rcs-agent'). No single probe is authoritative — a partial model can include some operations while omitting others. Treat any Invalid choice '<operation>' at ANY step as a stale/partial model and resolve it per prerequisite 3 (check for a stale ~/.aws/models/ override first, otherwise give the user the AWS CLI v2 upgrade commands; boto3 fallback for locked environments). Separately note v1 vs v2: v1 changes the URL-field behavior in the registration steps (handled where it matters below).

  2. Credentials — run aws sts get-caller-identity --region <REGION>. Pass --region so STS uses the regional endpoint (sts.<region>.amazonaws.com); the global endpoint (sts.amazonaws.com) is legacy and best avoided. If it fails, ask the user how they authenticate and follow the matching branch:

    • Named profile: ask for the profile name; from now on append --profile <PROFILE> to EVERY aws command in this task. Missing it on even one command causes NoCredentials/ExpiredTokenException.

    • IAM Identity Center (SSO): aws sso login --profile <PROFILE>, then treat as named profile.

    • Access keys: aws configure (or aws configure --profile <PROFILE>). Prefer IAM Identity Center (SSO) or assumed-role ephemeral credentials over long-lived access keys. If access keys are used, rotate them regularly and never embed them in code. Re-run aws sts get-caller-identity --region <REGION> until it succeeds. Ensure AWS CloudTrail is enabled in the account so all pinpoint-sms-voice-v2 API calls (agent creation, registration submission, message sends) are logged for auditing and abuse detection.

  3. Service access AND RCS capability in one probe — describe-spend-limits is the WRONG canary (it exists in old CLIs too). Probe with an RCS-specific read-only call:

    aws pinpoint-sms-voice-v2 describe-rcs-agents --region <REGION> >/dev/null && echo "RCS APIs: OK"
    • AccessDeniedException → the identity needs the required sms-voice: permissions, scoped to the specific actions these steps use rather than the sms-voice:* wildcard (see Failure modes).

    • Invalid choice '<operation>' on this probe OR on ANY pinpoint-sms-voice-v2 operation at any later step (a stale or partial local model can carry some operations while omitting others, e.g. the RCS agent control-plane present but send-rcs-message absent) → the local client's service model is stale or partial, not a permissions or account problem. Resolve it:

      1. Check for a stale local model override first — a leftover file shadows the SDK's bundled model, so even a current CLI stays broken:

        ls ~/.aws/models/pinpoint-sms-voice-v2/

        If present, rename it away (e.g. mv ~/.aws/models/pinpoint-sms-voice-v2 ~/.aws/models/pinpoint-sms-voice-v2.archive) and retry the operation.

      2. Otherwise the installed CLI is too old — tell the user their AWS CLI predates this operation and give them the commands to upgrade to the latest AWS CLI v2 (run aws --version, then follow the AWS CLI install/update guide). You do NOT need to determine which CLI version introduced the operation — upgrading to the latest v2 and retrying is sufficient; don't research CLI version history. Do not run the upgrade yourself — a CLI upgrade is a privileged system change the user should perform. Retry the operation after upgrading.

      3. Locked environment that cannot upgrade → boto3 fallback (it often carries the operations even when the CLI does not). Probe for the SPECIFIC operation that failed, expressed in boto3 PascalCase (e.g. create-rcs-agent → CreateRcsAgent, send-rcs-message → SendRcsMessage) — do not assume one operation's presence implies another's, since a partial model can carry some while omitting others:

        python3 -c "import boto3; c=boto3.client('pinpoint-sms-voice-v2', region_name='<REGION>'); print('<PascalCaseOp>' in c.meta.service_model.operation_names)"

        True → run the RCS-agent steps through boto3 (parameters are PascalCase: RegistrationId, SelectChoices=[...], AttachmentBody=<bytes> — vs the CLI's --kebab-case). False → pip install -U boto3 (AWS software) and re-check; if STILL false, apply the same stale-override check to ~/.aws/models/ and rename it away.

      Any resources already created before the failure (agent, registration, verified tester) remain valid — once the client is fixed, resume the flow; nothing needs recreating.

  4. Image tooling — nothing to install. If you will GENERATE brand assets (step 1, option B), the primary path is a Python-stdlib PNG generator (zlib + struct — already present with python3). Optionally run which rsvg-convert: if it is ALREADY installed you may render a nicer SVG design instead, but do NOT install it — the stdlib generator is sufficient for this quickstart. Skip this check if the user provides their own images.

  5. The user's phone — an Android phone with RCS enabled, or an iPhone on iOS 18+, with the number in PHONE. Confirm the user has it in hand; they will tap an invitation later.

  6. Input validation — validate user-provided values (E.164 for phones, URL scheme allowlist, length checks) before passing to APIs.

  7. Inputs to collect — ask the user:

    • Brand name.

    • Registration details. By default you fill these with synthetic placeholder values (.example.com contacts, an invented description, a safe accent color). Do NOT open by interviewing the user for each field or by running live field discovery to decide what to ask — go straight to the synthetic defaults. Be explicit that the data is synthetic: show the user the full set of values you will use and let them opt out — "these are placeholder values for a test agent; tell me if you'd rather provide your own for any field." Get their confirmation (or their replacements) before submitting; never submit invented data silently. If the user supplies real values (contact names, emails, phone numbers, business details), treat them as sensitive business-identifying PII: use them only to populate the registration and to confirm back, and do not repeat them in responses or write them to logs beyond what confirmation requires.

    • Do they have their own logo and banner images? If yes, get the file paths — you will validate them in step 1 (option A). If no, generate placeholder assets (option B, quick mode), which fall under the same synthetic-data disclosure above.

Steps

1. Brand assets — validate the user's own, or generate them

The registration requires a logo (query dimensions from describe-registration-field-definitions; expected 224×224 px, under 50 KB, PNG or JPEG) and a banner (query dimensions from describe-registration-field-definitions; expected 1440×448 px, under 200 KB, PNG or JPEG). The registration API rejects anything outside these constraints, so verify BEFORE uploading either way. Both images are uploaded with create-registration-attachment and then referenced by their returned attachment IDs in the agentDetails.logoImage and agentDetails.bannerImage registration field values (see the attachment upload and field-value steps below).

Option A — the user provides their own images

Copy their files to brand-assets/logo.png and brand-assets/banner.png (or .jpg), then validate format, dimensions, and size with this stdlib-only check:

# Dimensions per describe-registration-field-definitions (Step 5).
python3 - brand-assets/logo.png 224 224 51200 <<'EOF' && echo "logo OK"
import struct, sys
path, w, h, maxb = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
data = open(path, 'rb').read()
if len(data) > maxb: sys.exit(f"FAIL: {len(data)} bytes exceeds {maxb}")
if data[:8] == b'\x89PNG\r\n\x1a\n':
    iw, ih = struct.unpack('>II', data[16:24])
elif data[:2] == b'\xff\xd8':
    i, iw, ih = 2, None, None
    while i < len(data) - 9:
        if data[i] != 0xFF: i += 1; continue
        m = data[i+1]
        if 0xC0 <= m <= 0xCF and m not in (0xC4, 0xC8, 0xCC):
            ih, iw = struct.unpack('>HH', data[i+5:i+9]); break
        i += 2 + struct.unpack('>H', data[i+2:i+4])[0]
    if iw is None: sys.exit("FAIL: could not parse JPEG dimensions")
else:
    sys.exit("FAIL: not PNG or JPEG (SVG and other formats are rejected)")
sys.exit(0 if (iw, ih) == (w, h) else f"FAIL: {iw}x{ih}, need exactly {w}x{h}")
EOF
python3 - brand-assets/banner.png 1440 448 204800 <<'EOF' && echo "banner OK"
import struct, sys
path, w, h, maxb = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
data = open(path, 'rb').read()
if len(data) > maxb: sys.exit(f"FAIL: {len(data)} bytes exceeds {maxb}")
if data[:8] == b'\x89PNG\r\n\x1a\n':
    iw, ih = struct.unpack('>II', data[16:24])
elif data[:2] == b'\xff\xd8':
    i, iw, ih = 2, None, None
    while i < len(data) - 9:
        if data[i] != 0xFF: i += 1; continue
        m = data[i+1]
        if 0xC0 <= m <= 0xCF and m not in (0xC4, 0xC8, 0xCC):
            ih, iw = struct.unpack('>HH', data[i+5:i+9]); break
        i += 2 + struct.unpack('>H', data[i+2:i+4])[0]
    if iw is None: sys.exit("FAIL: could not parse JPEG dimensions")
else:
    sys.exit("FAIL: not PNG or JPEG (SVG and other formats are rejected)")
sys.exit(0 if (iw, ih) == (w, h) else f"FAIL: {iw}x{ih}, need exactly {w}x{h}")
EOF

If a check FAILs, tell the user exactly what's wrong and offer fixes:

  • Wrong dimensions — resize (do not stretch a mismatched aspect ratio without asking; offer to pad instead): with rsvg-convert unavailable, sips -z 224 224 logo.png works on macOS, or regenerate at the right size from their source file.

  • Too large — re-export as PNG with fewer colors, or as JPEG quality ~85.

  • Wrong format (SVG, WebP, HEIC...) — convert to PNG first.

Also pick the accent color to complement their images (ask, or sample a dominant dark color) — the contrast rule below still applies.

Option B — generate the assets

Work in a fresh directory (e.g. mkdir -p ~/hello-eum-<brand-slug> && cd into it) — a shared brand-assets/ dir may hold leftovers from previous runs for a different brand; don't reuse or blindly overwrite files you didn't create this run without checking them first.

Pick an accent color with at least 4.5:1 contrast against white. Safe choices: #0D47A1 #1B5E20 #BF360C #B71C1C #4A148C. Never light or pastel colors — the review rejects them with ACCENT_COLOR_CONTRAST_INSUFFICIENT.

Generate both images with this stdlib-only script (nothing to install — zlib + struct only): a solid accent-color background with simple white geometry (centered disc on the logo, a horizontal band on the banner). Substitute the accent hex you picked:

mkdir -p brand-assets
python3 - brand-assets "<ACCENT_HEX>" <<'EOF'
import os, struct, sys, zlib
outdir, accent = sys.argv[1], sys.argv[2].lstrip('#')
bg = tuple(int(accent[i:i + 2], 16) for i in (0, 2, 4))
fg = (255, 255, 255)
def chunk(tag, data):
    return (struct.pack('>I', len(data)) + tag + data
            + struct.pack('>I', zlib.crc32(tag + data) & 0xFFFFFFFF))
def write_png(path, w, h, pixel):
    rows = bytearray()
    for y in range(h):
        rows.append(0)  # PNG filter type None for this scanline
        for x in range(w):
            rows += bytes(pixel(x, y))
    ihdr = struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)
    with open(path, 'wb') as f:
        f.write(b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', ihdr)
                + chunk(b'IDAT', zlib.compress(bytes(rows), 9)) + chunk(b'IEND', b''))
    print(f'{path}: {w}x{h}, {os.path.getsize(path)} bytes')
# Dimensions per describe-registration-field-definitions (Step 5).
write_png(os.path.join(outdir, 'logo.png'), 224, 224,
          lambda x, y: fg if (x - 112) ** 2 + (y - 112) ** 2 <= 64 ** 2 else bg)
write_png(os.path.join(outdir, 'banner.png'), 1440, 448,
          lambda x, y: fg if 200 <= y < 248 else bg)
EOF
ls -la brand-assets/*.png

Middle tier — if Pillow is already installed (python3 -c "import PIL" succeeds; do NOT install it), it renders proper brand-name text, which the stdlib generator cannot. Two traps observed live: font paths vary by distro — never hardcode; discover with fc-list : file | grep -iE "sans|noto|dejavu" | head and pass a discovered .ttf to ImageFont.truetype(path, size). And if the font fails to load, Pillow silently falls back to a tiny bitmap font — the banner text renders tiny and off-scale; validate visually or check the font loaded before trusting the output.

IF rsvg-convert is already installed (which rsvg-convert from the prerequisites), you may instead write logo/banner SVGs (solid accent-color background, a basic shape icon, brand name text, Arial font) and render them for a nicer design: rsvg-convert -w 224 -h 224 brand-assets/logo.svg -o brand-assets/logo.png and rsvg-convert -w 1440 -h 448 brand-assets/banner.svg -o brand-assets/banner.png. Do NOT install it — the stdlib generator above is sufficient for the quickstart.

Checkpoint (both options): files exist, logo under 50 KB, banner under 200 KB, dimensions exact. Generated assets can be validated with the option A script too. If a generated file is over the size limit, simplify the design (fewer distinct colors/shapes) and regenerate.

2. Create the agent

The agent is an empty container — its name, colors, and images all come from the registration in the next steps, not from this call.

aws pinpoint-sms-voice-v2 create-rcs-agent \
  --deletion-protection-enabled \
  --region <REGION>

Save RcsAgentId as AGENT_ID.

aws pinpoint-sms-voice-v2 create-registration \
  --registration-type TEST_RCS_LAUNCH_REGISTRATION \
  --region <REGION>

Save RegistrationId as REG_ID. Link it (this must happen before submitting):

aws pinpoint-sms-voice-v2 create-registration-association \
  --registration-id <REG_ID> \
  --resource-id <AGENT_ID> \
  --region <REGION>
4. Upload the brand images

--attachment-body and --attachment-url cannot be combined; use --attachment-body with a local fileb:// path:

aws pinpoint-sms-voice-v2 create-registration-attachment \
  --attachment-body fileb://brand-assets/logo.png --region <REGION>

Save RegistrationAttachmentId as LOGO_ID. Repeat for banner.png, save as BANNER_ID.

5. Fill every registration field

Each field has a type that determines the CLI parameter. Using the wrong one fails:

Field typeCLI parameter
TEXT--text-value "<value>"
SELECT--select-choices "<value>"
ATTACHMENT--registration-attachment-id "<id>"

(--field-values does not exist. Do not invent it.)

Query the field definitions FIRST and drive your field-setting from that output — the table below is illustrative defaults, not the source of truth (field sets and SELECT choices can drift by service version):

aws pinpoint-sms-voice-v2 describe-registration-field-definitions \
  --registration-type TEST_RCS_LAUNCH_REGISTRATION --region <REGION>

Inspect the full response — each FieldDefinition object contains the field requirement, type, path, validation constraints, and any conditional dependencies. Do not filter this output; conditional field requirements depend on context from the complete definition shape.

SELECT options live under SelectValidation.Options as a list of plain strings (not objects) — a parser expecting {OptionName: ...} will throw. If any command below fails on an unknown field or the submit step reports a missing one, the definitions output wins.

Set every TEXT field (one put-registration-field-value call each, same shape):

aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.brandName" --text-value "<BRAND_NAME>" --region <REGION>

Repeat for each row (quick-mode defaults shown). If you batch these with a shell loop or function, inline --region (and --profile) literally in the command — passing multi-word flag strings through a variable (R="--region us-east-1") breaks on word-splitting:

FieldPathValueConstraint
agentDetails.brandNamebrand name2–65 chars
agentDetails.serviceName<BRAND_NAME> RCS Agent1–100 chars
agentDetails.senderDisplayNamebrand name1–40 chars
agentDetails.agentDescriptionone-liner you inventmax 100 chars
agentDetails.accentColorthe hex from step 1#RRGGBB, 4.5:1 vs white
agentDetails.contactPhoneNumber+12065550100E.164
agentDetails.contactPhoneLabelCall Us1–25 chars
agentDetails.contactEmailAddresshello@<brand-slug>.example.comemail
agentDetails.contactEmailLabelEmail Us1–25 chars
agentDetails.contactWebsitehttps://www.<brand-slug>.example.comURL
agentDetails.contactWebsiteLabelVisit Website0–25 chars
agentDetails.privacyPolicyUrlhttps://www.example.com/privacyURL
agentDetails.privacyPolicyLabelPrivacy Policy0–25 chars
agentDetails.termsAndConditionsUrlhttps://www.example.com/termsURL
agentDetails.termsAndConditionsLabelTerms and Conditions0–25 chars
agentDetails.monthlyRcsVolume10001–6 digits
complianceKeywords.helpResponseReply STOP to opt out. For help, contact <email>1–160 chars
complianceKeywords.stopResponseYou have been unsubscribed. No more messages will be sent.1–160 chars

Note on the three URL fields (contactWebsite, privacyPolicyUrl, termsAndConditionsUrl): on AWS CLI v2 the plain --text-value "https://..." form works. On AWS CLI v1, a legacy "paramfile" feature fetches https:// parameter values as remote files, so the command fails — either client-side ("Unable to retrieve ... parameter file" / usage error) or server-side (ValidationException: INVALID_PARAMETER Fields="textValue" when the CLI fetched the page and sent its HTML), depending on whether the URL resolves. If you see that error (or you detected CLI v1 in prerequisites), use the JSON form, which bypasses paramfile:

aws pinpoint-sms-voice-v2 put-registration-field-value --region <REGION> --cli-input-json \
  '{"RegistrationId":"<REG_ID>","FieldPath":"agentDetails.contactWebsite","TextValue":"https://www.example.com"}'

Set the SELECT fields (note --select-choices, not --text-value):

aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.useCase" --select-choices "MULTI_USE" --region <REGION>
aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.averageMonthlyRcsFrequency" --select-choices "10" --region <REGION>

Set the remaining SELECT field — easy to miss because older references omit it:

aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.billingCategory" --select-choices "CONVERSATIONAL" --region <REGION>

Set the ATTACHMENT fields:

aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.logoImage" --registration-attachment-id "<LOGO_ID>" --region <REGION>
aws pinpoint-sms-voice-v2 put-registration-field-value --registration-id <REG_ID> \
  --field-path "agentDetails.bannerImage" --registration-attachment-id "<BANNER_ID>" --region <REGION>
6. Audit, then submit and poll for approval

Do not trust your own count of fields set — audit against the service before submitting. Compare what is SET against what is REQUIRED:

aws pinpoint-sms-voice-v2 describe-registration-field-values \
  --registration-id <REG_ID> --region <REGION> \
  --query 'RegistrationFieldValues[].FieldPath' --output text | tr '\t' '\n' | sort > /tmp/set.txt
aws pinpoint-sms-voice-v2 describe-registration-field-definitions \
  --registration-type TEST_RCS_LAUNCH_REGISTRATION --region <REGION> \
  --query "RegistrationFieldDefinitions[].FieldPath" --output text | tr '\t' '\n' | sort > /tmp/req.txt
comm -13 /tmp/set.txt /tmp/req.txt

Do not hardcode a filter on the requirement value — derive the field set from the full definition and let the live schema decide applicability. Any line printed is a defined field you have not set; confirm from the field's own definition whether it applies before continuing. Then submit:

aws pinpoint-sms-voice-v2 submit-registration-version \
  --registration-id <REG_ID> --region <REGION>

Poll BOTH statuses every 15 seconds — they move independently:

aws pinpoint-sms-voice-v2 describe-registrations --registration-ids <REG_ID> \
  --query 'Registrations[0].RegistrationStatus' --region <REGION>
aws pinpoint-sms-voice-v2 describe-rcs-agents \
  --query "RcsAgents[?RcsAgentId=='<AGENT_ID>'].TestingAgent.Status" --region <REGION>

Expected: registration SUBMITTED → REVIEWING → COMPLETE, and TestingAgent.Status: ACTIVE. Typically under 5 minutes; don't worry before 10. If the registration lands in REQUIRES_UPDATES, see Failure modes — do not create a new version blindly.

Checkpoint: tell the user the agent is live. To let them visually see the branding they just configured (logo, banner, accent color, contact details as they render), give them the console link to the agent page: https://<REGION>.console.aws.amazon.com/sms-voice/home?region=<REGION>#/rcs-agents?agent-id=<AGENT_ID>

If they are not already signed in, tell them to sign in to the AWS console first, then open the link. (This is view-only confirmation; no changes are needed here.)

7. Add the user's phone as a verified tester

Wait at least 120 seconds after agent creation before this call, or it can fail silently.

aws pinpoint-sms-voice-v2 create-verified-destination-number \
  --destination-phone-number <PHONE> \
  --rcs-agent-id <AGENT_ID> \
  --region <REGION>

Tell the user, verbatim enough that they can act on it:

  • "You'll get a tester invitation on your phone in 2–20 minutes, from a sender called RBM Tester Management."

  • "On iPhone, it may land in the Unknown Senders folder in Messages."

  • "Tap Make me a tester when it arrives, then tell me."

While waiting, verify the invitation went out:

aws pinpoint-sms-voice-v2 describe-verified-destination-numbers \
  --filters Name=rcs-agent-id,Values=<AGENT_ID> --region <REGION> \
  --query 'VerifiedDestinationNumbers[].{Phone:DestinationPhoneNumber,Status:Status}'

Wait for the user to confirm they tapped it (status becomes VERIFIED).

8. Clear send blockers, then send the first message

Two account-level settings can block the send. Check both first:

# Is the destination country blocked by a protect configuration?
aws pinpoint-sms-voice-v2 describe-protect-configurations --region <REGION>
# Is the phone on the opt-out list (e.g. from a previous STOP)?
aws pinpoint-sms-voice-v2 describe-opted-out-numbers \
  --opt-out-list-name Default --region <REGION>

Interpreting the protect output: a protect configuration only applies to this send if it is the account default (AccountDefault: true), associated with a configuration set you pass on the send, or passed explicitly as ProtectConfigurationId. Other protect configurations in the account are inert for this quickstart — do not "fix" them.

Fixes, only if needed (each modifies account-wide state — confirm with the user first). ⚠️ These are not scoped to this agent or send: a protect-configuration country-rule change and an opt-out-list deletion affect every origination identity in the account (all agents, phone numbers, and pools), and opt-out removal re-enables messaging to a number that previously sent STOP. Change them only when you own the account-level policy and understand the blast radius:

  • Country blocked: aws pinpoint-sms-voice-v2 update-protect-configuration-country-rule-set --protect-configuration-id <ID> --number-capability SMS --country-rule-set-updates '{"US":{"ProtectStatus":"ALLOW"}}' --region <REGION> (the SMS capability governs RCS sends too)

  • Opted out: aws pinpoint-sms-voice-v2 delete-opted-out-number --opt-out-list-name Default --opted-out-number <PHONE> --region <REGION>

Send:

aws pinpoint-sms-voice-v2 send-rcs-message \
  --destination-phone-number <PHONE> \
  --origination-identity <AGENT_ID> \
  --rcs-message-content '{"Content":{"TextMessage":{"Body":"Hello from <BRAND_NAME>! This is your first RCS message."}}}' \
  --message-traffic-type TRANSACTIONAL \
  --region <REGION>

The message text goes in --rcs-message-content as JSON (Content.TextMessage.Body); there is no --message-body flag. --message-traffic-type takes TRANSACTIONAL or PROMOTIONAL.

Confirm the current shape against your CLI, don't trust these flags blindly. The pinpoint-sms-voice-v2 request shape has changed across versions. Before sending, generate the skeleton from the installed CLI and fill it in, so you use whatever shape your CLI expects rather than a possibly-stale example:

aws pinpoint-sms-voice-v2 send-rcs-message --generate-cli-skeleton input > send-rcs.json
# edit send-rcs.json (DestinationPhoneNumber, OriginationIdentity,
# RcsMessageContent.Content.TextMessage.Body, MessageTrafficType), then:
aws pinpoint-sms-voice-v2 send-rcs-message --cli-input-json file://send-rcs.json --region <REGION>

If a documented flag is rejected (Unknown options / Invalid choice), regenerate the skeleton and use its fields — this is the same reactive stale-model handling as prerequisite 3, applied to parameter shape instead of operation existence.

<!-- -->

Production note: For production, add --configuration-set-name <CONFIG_SET_NAME> to enable delivery telemetry (create a configuration set with an event destination first). Pass message bodies via --cli-input-json from a file to avoid sensitive content persisting in shell history.

Save MessageId — and know what it means: MessageId proves the service ACCEPTED the message, not that it was delivered. This quickstart sends without a configuration set, which means there is NO delivery telemetry — you cannot look up what happened to a message downstream. Set delivery expectations accordingly:

  1. Ask the user: "Check your phone — you should see a branded message from <BRAND_NAME> with your logo. On iPhone, check Unknown Senders. Give it up to 2 minutes."

  2. If nothing arrives: resend the same command once before any deeper diagnosis. This is the cheapest test and frequently resolves it.

  3. If the resend also doesn't arrive, check the device, not the account (the API accepted both sends): Android — Messages → Settings → RCS chats → status must be "Connected"; iPhone — Settings → Apps → Messages → RCS Messaging enabled (iOS 18+, carrier support).

  4. Do NOT build logging infrastructure mid-quickstart — but enabling CloudTrail and a configuration set with event destinations (SNS with SSE/KMS, CloudWatch Logs with CMK, or Kinesis Firehose with server-side encryption) is REQUIRED before production use. Encrypt CloudTrail logs and CloudWatch Log groups with a KMS CMK, as message bodies and delivery metadata may contain sensitive information. Create CloudWatch alarms on send failure rates, throttling events, and spend limit utilization to detect anomalous activity before it impacts production. If the user wants real delivery telemetry, that is a configuration set + event destination — finish this task first, then follow the configuration-sets-and-event-destinations guide.

9. Prove inbound works — no infrastructure needed

Set up an auto-response keyword:

aws pinpoint-sms-voice-v2 put-keyword \
  --origination-identity <AGENT_ID> \
  --keyword HELLOEUM \
  --keyword-action AUTOMATIC_RESPONSE \
  --keyword-message "Inbound works! Your message reached AWS and this reply came back automatically." \
  --region <REGION>

If the auto-response doesn't come back, check these IN ORDER before anything else — this step fails silently, and without a configuration set there is no inbound telemetry to inspect:

  1. Keyword match is exact. The message must be ONLY the keyword — autocorrect adding punctuation (HELLOEUM.), predictive text, or leading/trailing words all miss silently.

  2. It must arrive over RCS, not SMS. An SMS from the device won't route to the agent's keyword handler. Confirm the thread shows RCS before blaming configuration.

  3. Allow ~60s on a freshly activated agent.

  4. The console deep link below is the DETERMINISTIC path — it pre-fills the exact keyword over the right channel, removing all three ambiguities. Prefer it for the first test.

Walk the user through the console inbound test:

  1. Open the agent's Testing tab in the console (sign in first if needed): https://<REGION>.console.aws.amazon.com/sms-voice/home?region=<REGION>#/rcs-agents?agent-id=<AGENT_ID>&tab=testing

  2. Click Inbound deep link, enter HELLOEUM as the message body, generate the link.

  3. Scan the QR code with the phone — the message is pre-filled. Send it.

  4. The auto-response should arrive within seconds.

Verify

All three must be true, confirmed by the user and by API:

  1. TestingAgent.Status is ACTIVE and the verified number shows VERIFIED.

  2. The user received the branded outbound message (step 8).

  3. The user sent HELLOEUM and received the auto-response (step 9).

Print a summary table for the user: brand, AGENT_ID, region, console link.

Next steps

  • Send rich messages — cards, carousels, suggestion buttons, webviews: the send-apis guide. When a message includes media (SendRcsMessage FileMessage or card media), the FileUrl and ThumbnailUrl fields accept either an https:// URL or an s3:// URI (pattern ^(https://|s3://).+$) pointing to a supported media file — image, video, audio, or PDF (see the RCS file messages guide for the current supported media types and limits). If the media is already reachable at a URL, pass that URL directly — do NOT create an S3 bucket to re-host media you already have a URL for. Use s3:// only when the media already lives in S3 (the service then downloads, rehosts, and presigns it; the bucket needs a resource policy granting sms-voice.amazonaws.com read access, scoped with aws:SourceAccount — and aws:SourceArn when a specific resource is known — to prevent cross-account confused-deputy access, default encryption at rest enabled (SSE-S3 or SSE-KMS), and a deny-non-TLS statement ("Condition":{"Bool":{"aws:SecureTransport":"false"}}) so media is only retrieved over HTTPS). Do NOT guess or hand-construct a media URL (e.g. inventing a CDN path) — use a URL the user provides or one you have verified resolves to the file.

  • React to inbound messages with code — wire an SNS topic (with SSE/KMS, aws:SourceArn/aws:SourceAccount condition keys, and authorized subscriptions only) and Lambda: the two-way-inbound-lambda guide

  • Rate-limit outbound sends: Implement application-side throttling on message sends (e.g., per-recipient and per-minute caps) to prevent abuse and stay within service quotas.

  • Go to production — a test agent only reaches verified testers. To message real customers you need a production agent launch registration: mechanics in references/customer-go-to-production-guide.md, per-country requirements (always discovered live) in the registration-requirements-by-country guide

  • Raise the account's monthly spend limit before serious testing (check the current default via aws pinpoint-sms-voice-v2 describe-spend-limits --region <REGION>): request quota L-2325465C, then set-text-message-spend-limit-override.

Security Considerations

  • Ephemeral credentials: Prefer IAM Identity Center (SSO)/assumed-role over long-lived access keys.

  • Input validation: Validate all user inputs (E.164, URL allowlist, length) before API calls.

  • Encryption: Encrypt CloudTrail logs and CloudWatch Log groups with KMS CMK.

  • SNS topics: Enable SSE with KMS, restrict with aws:SourceArn/aws:SourceAccount, authorize subscriptions.

  • Rate limiting: Throttle outbound sends per-recipient and per-minute.

  • Deletion protection: Enabled by default; confirm with user before cleanup.

Failure modes

Error / symptomCauseFix
AccessDeniedException on any callMissing IAM permissions or expired sessionRe-auth (aws sso login --profile <PROFILE> or aws configure); identity needs the required scoped sms-voice: actions (not the sms-voice:* wildcard)
ExpiredTokenExceptionSession expired mid-taskRe-auth, retry the same command
Invalid choice '<operation>' (any pinpoint-sms-voice-v2 op, at any step)Local client's service model is stale or partial — a partial model can carry some ops (e.g. control-plane describe-rcs-agents) while omitting others (e.g. data-plane send-rcs-message)Not permissions/account. First check for a stale local override: ls ~/.aws/models/pinpoint-sms-voice-v2/ — rename it away if present (it shadows the SDK) and retry. If no override, the CLI is too old — give the user the commands to upgrade to the latest AWS CLI v2 (aws --version, then the install/update guide); do not run the upgrade yourself. Locked environment → boto3 fallback; if boto3 is also stale, apply the same override check. Resources already created remain valid — resume after fixing
Registration REQUIRES_UPDATESA field was denieddescribe-registration-field-values --registration-id <REG_ID> → find DeniedReason → fix the cause → create-registration-version → re-set ALL fields (new versions start empty) → resubmit
ACCENT_COLOR_CONTRAST_INSUFFICIENTColor too lightPick a darker hex from step 1's list; full re-populate as above
Unknown/missing field errors at submitField set drifts by CLI/service versionReconcile against describe-registration-field-definitions output; set what it lists
No tester invitation after 20 minToo fast after creation, or agent not ACTIVEConfirm 120 s elapsed post-creation and TestingAgent.Status: ACTIVE; delete and re-create the verified number
DESTINATION_COUNTRY_BLOCKED_BY_PROTECT_CONFIGURATIONCountry rule set blocks destinationStep 8 protect-configuration fix
DESTINATION_PHONE_NUMBER_OPTED_OUTNumber previously sent STOPStep 8 opt-out fix, then resend
MONTHLY_SPEND_LIMIT_REACHEDDefault spend limit exhausted (check current default with describe-spend-limits)Quota increase L-2325465C, then set-text-message-spend-limit-override
Message arrives as SMS, not RCSAgent not ACTIVE, device lacks RCS, or wrong origination identityVerify agent status; confirm device RCS support; --origination-identity must be the rcs-… ID or its full ARN
Message doesn't arrive at allNot a verified testerTest agents ONLY deliver to VERIFIED destination numbers — check step 7
MessageId returned but nothing arrives (number IS verified)No delivery telemetry exists without a configuration set; cause is usually device-sideWait up to 2 min → resend once → check device RCS state (Android: RCS chats "Connected"; iPhone: RCS Messaging on). Don't build logging infra mid-task; for telemetry see the configuration-sets-and-event-destinations guide

Cleanup (destructive — confirm with the user first)

# <VDN_ID> came from the create-verified-destination-number response (or describe-verified-destination-numbers)
aws pinpoint-sms-voice-v2 delete-verified-destination-number --verified-destination-number-id <VDN_ID> --region <REGION>
aws pinpoint-sms-voice-v2 delete-registration --registration-id <REG_ID> --region <REGION>
aws pinpoint-sms-voice-v2 delete-registration-attachment --registration-attachment-id <LOGO_ID> --region <REGION>
aws pinpoint-sms-voice-v2 delete-registration-attachment --registration-attachment-id <BANNER_ID> --region <REGION>
aws pinpoint-sms-voice-v2 update-rcs-agent --rcs-agent-id <AGENT_ID> --no-deletion-protection-enabled --region <REGION>
aws pinpoint-sms-voice-v2 delete-rcs-agent --rcs-agent-id <AGENT_ID> --region <REGION>

If delete-rcs-agent fails with ConflictException RESOURCE_NOT_EMPTY right after the registration delete, the testing agent is still tearing down asynchronously — wait ~20-30s and retry. Attachment deletes can hit the same lag (RESOURCE_DELETION_NOT_ALLOWED); same fix.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
AWS
Publisher
AWS (Official)
Source
aws/agent-toolkit-for-aws · skills/specialized-skills/customer-communications-skills/aws-sms-voice · commit 0a218f8
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • NOTICE (added by AutoGineer from NOTICE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by AWS or AWS.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download aws-sms-voice.zip