Providers
Sent
Uses the Ahena CLI (beta). Install it with npm install -g @ahena/cli, or prefix commands with npx @ahena/cli. See Installation. The dashboard covers projects, connections, Doctor, the Stack Graph, plans and approvals without it.
Overview
Sent (sent.dm) is one messaging API for SMS, WhatsApp and RCS, with automatic channel routing
and fallback, templates that adapt per channel, sender profiles, and delivery webhooks. Ahena
checks that your account, sender profile and channels are ready to send (including 10DLC and
other registrations), creates the message templates and the delivery webhook your app declares,
and generates the provider-neutral ahena.sms code. Your app sends straight to Sent with its own
API key. Ahena never sends a message and never reads contacts, conversations or message history.
Package: @ahena/provider-sent · Category: sms
Capabilities: sms, whatsapp, rcs, sender-profiles, registration, templates, webhooks
Maturity: verified against a fake of the API; not yet verified against the real service.
Connection
| Field | Secret | Used for |
|---|---|---|
SENT_API_KEY |
yes | Every request, as the x-api-key header. |
SENT_API_KEY=… ahena connect sent -e production
A key belongs to one account, which GET /v3/me reports as an organization (it has sender
profiles), a standalone user, or a sender profile. An organization key can act for one of
its profiles with the x-profile-id header: set sms.senderProfile (the profile's id or name)
and Ahena sends that header on everything it reads or writes. A profile's own key can act only as
that profile.
Sent's keys are UUIDs with one format for every environment; sandboxing is a per-request body
flag ("sandbox": true), not a kind of key. So Ahena can't tell a "test" key from a "live" one
and never reports live on validation. (The OpenAPI document's security-scheme description
mentions sk_live_/sk_test_ prefixes; the authentication reference says otherwise, and Ahena
follows the reference.) Use a separate key per environment so each can be revoked alone.
Permissions
Sent keys have no scopes. The key's account may hold a role (owner, admin, developer, billing),
but Sent role-checks only profile and user management. Templates, webhooks, channels and
/v3/me need nothing beyond a valid key, and no endpoint reports what a key may do. Ahena
therefore declares permissionCheck: authentication-only: ahena connect proves the key works,
which is everything Sent can say. A denial shows up as AUTH_004 (403) in Doctor or apply.
Recommendations:
- Create a key just for Ahena (Development → API Keys) and give your app a different one.
- Ahena never sends messages (it doesn't call
POST /v3/messages), never reads contacts, conversations or messages, and drops webhook signing secrets, profile API keys, WhatsApp tokens, RCS agent contact details and brand/campaign KYC data from every response before anything is returned.
Capabilities
| Capability | Access | Verification | Changes | Refuses |
|---|---|---|---|---|
sms |
read-only | Sending; buying numbers or adding SMS markets; filing 10DLC brands or campaigns | ||
whatsapp |
read-only | Connecting or changing a WhatsApp Business Account | ||
rcs |
read-only | Requesting an RCS agent | ||
sender-profiles |
read-only | Creating, editing or deleting sender profiles | ||
registration |
manual | |||
templates |
writable | existence-only | CONFIRMATION_REQUIRED | Editing an existing template's content, category or language; deleting; AUTHENTICATION templates |
webhooks |
writable | read-back | CONFIRMATION_REQUIRED | Deleting or turning off webhooks; removing subscriptions; rotating secrets |
Templates are existence-only: Sent's template response has a name, status, category,
language, channels and variable names, but not the copy, so Ahena confirms the template exists
(and Doctor reports its approval and any difference in variables, category or language); it
can't compare the text. Webhooks are read back in full (URL, subscriptions, on/off); the signing
secret is never readable after creation.
Network: api.sent.dm only, enforced. Your webhook route is probed through Ahena's
credential-free probe. ahena lock tracks the acting profile, the SMS markets, WhatsApp and RCS
status, the declared templates' status and the webhooks.
Configure
sms: {
provider: "sent",
senderProfile: "…", // optional: a sender profile id or name (organization keys)
channels: ["sms", "whatsapp"], // optional, default ["sms"]: what Doctor checks is ready
templates: [
{
name: "order_shipped",
body: "Hi {{name}}, your Leo order {{order}} is on its way. Reply STOP to opt out.",
samples: { name: "Lucas", order: "1042" },
category: "UTILITY", // optional: MARKETING or UTILITY (Sent detects it otherwise)
language: "en_US", // optional (Sent detects it otherwise)
submitForReview: true, // optional, default true
},
],
webhook: { production: { endpoint: "https://…/api/webhooks/sent", events: ["message", "templates"] } },
},
Any key may be keyed by environment. body becomes Sent's shared multiChannel body, so the
same copy renders on SMS, WhatsApp and RCS; each {{name}} becomes Sent's {{index:variable}}
placeholder with the sample you give (reviewers see it). webhook.events takes Sent's event
types (message, templates, channel, contact, link, call) or one sub-type
(message.delivered); Ahena checks them against GET /v3/webhooks/event-types. Default:
message and templates.
| Change | Classification | Notes |
|---|---|---|
Create a template (POST /v3/templates, then PUT to set its name) |
CONFIRMATION_REQUIRED | Matched by name, so never duplicated. Sent has no name on create, so Ahena names it right after. Submitted for review unless submitForReview: false. The request carries an Idempotency-Key, so a retry after a lost response returns the same template. |
Submit a DRAFT template for review (PUT … {"submit_for_review": true}) |
CONFIRMATION_REQUIRED | Only submit_for_review; the content isn't touched. |
Create the delivery webhook (POST /v3/webhooks) |
CONFIRMATION_REQUIRED | Matched by URL. Named Ahena: <environment>. Sent returns the signing secret once; Ahena stores it encrypted as SENT_WEBHOOK_SECRET and never returns it. |
Add missing subscriptions to that webhook (PUT /v3/webhooks/{id}) |
CONFIRMATION_REQUIRED | The webhook's name, URL, retry count and timeout are sent back as read; subscriptions are only widened, never removed. |
Turn the webhook back on (PATCH …/toggle-status) |
CONFIRMATION_REQUIRED | Sent turns a webhook off after 10 consecutive failed events. Fix the route first. |
Template approval is asynchronous (Meta reviews it when a WhatsApp Business Account is
connected, Sent's compliance team otherwise, per channel). Creating a template is VERIFIED once
it exists; while it waits, Doctor reports sent.template.<name> as WARNING with a MANUAL fix
("wait for the review"), and Sent's templates.approved / templates.rejected webhook events
say when it's decided.
Refused outright: deleting anything; sending; editing an existing template's content (editing an
approved template takes it out of service until it's approved again; declare new copy under a new
name instead); template names starting with sent_ (reserved by Sent); webhook URLs that aren't
public https or embed credentials; numbers, SMS markets, WhatsApp, RCS, sender profiles and 10DLC
registrations (they're billed, registered or approved outside the API's reach, so they're MANUAL).
Generate
ahena generate sent [--framework nextjs]:
src/ahena/sms/index.ts: the provider-neutral interface shared with Twilio:export interface SendSms { to: string; body: string }andexport const sms = { async send(input): Promise<{ id: string }>, get raw() }.sendmapsbodyto Sent's free-formtexton thesmschannel and returns the recipient'smessage_id. Sent delivers free-form text only inside an open conversation: to a contact who never replied, send an approved template first, throughsms.raw(the@sentdm/sentdmclient):sms.raw.messages.send({ to: ["+1…"], template: { name: "order_shipped", parameters: { … } } }). Otherwise Sent accepts the send (202) and the message endsBLOCKEDwithCONVERSATION_TEMPLATE_REQUIRED, reported on themessage.blockedwebhook event.src/ahena/providers/sent.ts: the client, fromSENT_DM_API_KEY(the SDK's own variable), andsentProfile()for thex-profile-idheader fromSENT_PROFILE_ID..env.example.sent.src/app/api/webhooks/sent/route.ts(Next.js): verifies Sent's Svix-style signature before anything else:v1,+ base64 HMAC-SHA256 over{X-Webhook-ID}.{X-Webhook-Timestamp}.{raw body}with the base64-decoded secret (withoutwhsec_), compared in constant time, within 5 minutes. 401 when it doesn't verify, 500 whenSENT_WEBHOOK_SECRETisn't set, 400 for invalid JSON, 200 after handling. The SDKs don't verify webhooks, so this is the code to keep.
Limitations
- Ahena doesn't create sender profiles, buy numbers, add SMS markets, connect WhatsApp, request RCS agents, or file 10DLC brands and campaigns. They're billed or reviewed by carriers, Meta or The Campaign Registry. Doctor reports their status and the steps.
- A test key in production can't be detected: Sent has one key format.
- Ahena never edits or deletes an existing template, and can't read its copy back (only its
variables, category, language and status), so a change to
bodyis reported as drift, not applied. - AUTHENTICATION templates (Meta's OTP format), headers, footers, buttons, channel-specific bodies and MMS media aren't modelled: create those in the Sent Dashboard.
- Organization webhooks cloned onto every sender profile (
sender_profile) aren't managed. Ahena creates the webhook on the account it acts as. inspectshows the key's own account (and its profiles' names and status); channel status for a profile shows in Doctor andahena lockwhensenderProfileis set.- The template and webhook Idempotency-Keys are cached by Sent for 24 hours. Ahena double-checks a replayed create and creates for real if the earlier one was deleted since.
- The
@sentdm/sentdmSDK (0.42) has no/v3/sender-profilesor/v3/channelsresources and itsTemplatetype claims adefinitionthe API doesn't return; Ahena follows the API reference and OpenAPI document. Not run against the real Sent API yet.
Manual steps
- Create a sender, connect WhatsApp or request RCS in the Sent Dashboard (Channels) as Doctor lists; for US long codes, complete the 10DLC brand and campaign.
- Finish a sender profile's setup (Sender Profiles) if Doctor says it's incomplete.
- Wait for template reviews; revise and resubmit a rejected template in Sent, or declare new copy under a new name.
- After deploying the webhook route with
SENT_WEBHOOK_SECRET, send a test event from the webhook's page in the Sent Dashboard.
Doctor checks
| Id | Severity | Meaning |
|---|---|---|
sent.credentials |
PASS / FAIL | The key works; the account type and name. |
sent.sender_profile |
PASS / WARNING / FAIL | The declared profile exists and this key can act for it; its setup status (approved, or WARNING while incomplete/pending, FAIL when rejected). |
sent.channel.sms |
PASS / INFO / WARNING / FAIL | At least one SMS market is ACTIVE; no sender at all (FAIL in production, MANUAL). |
sent.channel.sms.market.<country>_<type> |
WARNING | A market isn't ACTIVE yet (10DLC, sender ID or document review), with Sent's reason and whose move it is. |
sent.channel.whatsapp / sent.channel.rcs |
PASS / INFO / WARNING / FAIL | Declared channel connected and ACTIVE; ACTION_NEEDED or INACTIVE fail; under review warns in production. |
sent.channel.unknown |
WARNING | sms.channels names something other than sms, whatsapp, rcs. |
sent.template.<name> |
PASS / WARNING / FAIL | Approved; waiting for review (MANUAL); DRAFT or missing (plannable fix); rejected, paused, disabled or revoked (MANUAL); several with the same name. |
sent.template.<name>.drift |
WARNING | Variables, category or language differ from ahena.config.ts. |
sent.webhook |
PASS / WARNING / FAIL | A webhook delivers to the endpoint, is on, and has the declared subscriptions (plannable fixes). |
sent.webhook.events |
FAIL | webhook.events names an event type Sent doesn't have. |
sent.webhook.failures |
WARNING | Consecutive failed deliveries (Sent turns the webhook off at 10). |
sent.webhook.endpoint |
PASS / WARNING / FAIL | One unsigned POST to your route: 401/403 is right; 2xx means it doesn't verify signatures; 404/405 offers a SAFE generate fix. |
sent.channels / sent.templates |
FAIL | Sent couldn't be read (the error says why, e.g. AUTH_004). |
Core also checks that SENT_WEBHOOK_SECRET is stored (by name) when a webhook is declared.
Disconnect behavior
Removes the connection and the key Ahena stored. Sent has no API to revoke keys: delete or disable Ahena's key in the Sent Dashboard → Development → API Keys. If your app uses the same key it stops sending, which is why Ahena should have its own. Sender profiles, channels, templates, webhooks and message history are untouched.
Set up step by step
Create the credential. Sent Dashboard (app.sent.dm) → Development → API Keys → Add API Key, name it
ahena-production, Create Key, and copy it. Use the organization's key if Ahena should manage a sender profile (then setsms.senderProfile), or the profile's own key.Minimum permissions. Sent keys have no scopes; any active key works. Its owner needs no admin role for what Ahena does (templates and webhooks aren't role-gated).
Connect.
SENT_API_KEY=… ahena connect sent -e productionPipe the key or type it at the hidden prompt; never pass it as an argument.
Verify the connection.
ahena inspect sent -e productionlists the account, its sender profiles, SMS markets, WhatsApp and RCS status, templates and webhooks (no secrets).Run Doctor.
ahena doctor -e production.Plan. Add the
smssection above toahena.config.ts, thenahena plan -e production(orahena configure sent -e productionto plan and apply one provider).Apply an allowed change.
ahena apply -e production. Creating the template and the webhook each need confirmation; the signing secret is stored asSENT_WEBHOOK_SECRET.Verify.
ahena plan -e productionshows no Sent changes;ahena doctor -e productionshows the webhookPASSand the template waiting for review (WARNING) until it's approved;ahena env secrets productionlistsSENT_WEBHOOK_SECRET(masked). Thenahena generate sent -e production --framework nextjs, set the variables from.env.example.sent(ahena env reveal production SENT_WEBHOOK_SECRET), deploy, and checksent.webhook.endpointpasses.Troubleshooting.
Symptom Cause Fix "Sent rejected the API key" Key mistyped, disabled or deleted Create a key (Development → API Keys) and reconnect. "locked this API key … (BUSINESS_002)" 10 failed authentications in a row Wait (1 minute, escalating), fix the key, retry. sent.sender_profileFAIL "Only an organization key…"A profile key with another profile's senderProfileConnect the organization's key, or that profile's own. sent.channel.sms.market.us_ten_dlcWARNING10DLC brand or campaign still in review, or something owed Follow the reason Doctor shows; reviews take days. sent.template.<name>WARNING "waiting for review"Meta or Sent hasn't decided yet Nothing to do; it stays BLOCKED for sends until approved. Apply: "CONFLICT_006" The template is in review; content is frozen Wait for the review to finish. Messages BLOCKED with CONVERSATION_TEMPLATE_REQUIREDsms.sendfree-form text to a contact with no open conversationSend an approved template first via sms.raw.sent.webhook.endpointFAIL "accepted an unsigned request"The route doesn't verify signatures Use the generated route (or verify X-Webhook-Signaturefirst).sent.webhook"turned off"10 consecutive failed events Fix the route, then ahena configure sent -e production.Disconnect and revoke.
ahena disconnect sent -e production, then delete or disable the key in the Sent Dashboard → Development → API Keys.