@sendoka/cli
v0.2.2
Published
Sendoka command-line tools for developer workflows.
Readme
sendoka CLI
Developer tools for the Sendoka API — send messages, tail messages as they are created, forward webhook deliveries locally, trigger synthetic events.
Five commands, all of them below. There is no key management, no domain management and no interactive login; use the dashboard or the REST API for the rest. Full reference: docs/developer-tools/cli.md.
Install
Not published to npm yet, and Sendoka does not hold the sendoka name there:
a package installed under that name from the registry is not this one. From a
checkout of the repository:
cd packages/cli
npm install && npm run build
npm link # puts `sendoka` on your PATHRequires Node 20+.
Configure
Every command authenticates with an API key.
export SENDOKA_API_KEY=sok_test_...
# For `listen` only: the endpoint's whsec_ signing secret, so forwarded
# deliveries are re-signed. --secret works too.
export SENDOKA_WEBHOOK_SECRET=whsec_...
# Optional. Defaults to https://www.sendoka.com.
export SENDOKA_BASE_URL=https://www.sendoka.comThe key needs the scope its endpoint needs: send:email / send:sms for the sends, read:messages for logs tail, read:webhooks for listen, write:webhooks for events trigger. A full-access key has all of them.
With a sok_test_* key, listen forwards only deliveries of test-mode events (test sends and events trigger fires): an endpoint also receives live traffic, and a test key cannot read it. Its list pages can come back empty with more to follow; listen starts below the first such page rather than forwarding older deliveries as new. To forward live deliveries, use a live key scoped to read:webhooks alone.
Every request counts against your org's /api/v1 rate limit (60/min on Free), the same budget your production sends use. listen makes 12 requests a minute and logs tail 24 at the default 5-second interval; on a Free org raise --interval, or give the CLI a key with its own per-key limit. Both wait out a 429's Retry-After and resume without skipping anything.
SENDOKA_SESSION_COOKIE is no longer read. listen and events trigger used to send it to /api/internal/* under the cookie name NextAuth uses over plain http, which the https host never reads — both answered 401 against www.sendoka.com. They now use the public /api/v1/webhooks routes with the key.
Usage
Send
sendoka send email \
--from [email protected] \
--to [email protected] \
--subject "Hello" \
--html "<p>From the CLI</p>"
sendoka send sms --from +15551234567 --to +15559876543 --body "Test"--to is comma-separated for several recipients; the flag is not repeatable. --template + --variables '{"name":"Mira"}' render a stored template instead of an inline body. Attachments, tags, scheduling and Idempotency-Key are not exposed here — use an HTTP client for those.
Tail messages
sendoka logs tail
sendoka logs tail --channel sms --interval 15Polls GET /api/v1/emails and GET /api/v1/sms by created_after and prints one line per new message, starting from now, for the key's environment. Each message is printed once, with its status when first seen; later transitions (delivered, bounced) are what webhooks are for. --days from the old aggregate version is ignored.
Forward webhook deliveries to a local URL
sendoka listen --endpoint whk_xxx --forward-to http://localhost:3001/hooksPolls GET /api/v1/webhooks/{id}/deliveries?include=payload for that endpoint and re-POSTs each delivery created after the command started to --forward-to — body and headers as production sends them, signed with the endpoint secret (--secret or SENDOKA_WEBHOOK_SECRET), so the SDK's verifyWebhookSignature passes and your handler can keep verification on. Without a secret it warns and forwards unsigned. Avoids ngrok for local webhook dev.
Trigger a synthetic event
sendoka events trigger message.bounced --endpoint whk_xxx
sendoka events trigger message.bounced --endpoint whk_xxx --data '{"channel":"sms"}'POST /api/v1/webhooks/{id}/test-fire: fires a properly-signed test delivery at one of your webhook endpoints. Payloads carry data.test: true and "environment": "test" so your handler can distinguish them; --data (a JSON object) is merged over the synthetic data but cannot change those markers or the synthetic message_id. --event message.bounced is accepted as an equivalent to the positional form. Combine with listen for a fully local loop.
Exit codes
0 success, 1 the command threw (HTTP error, missing env var, bad --variables / --data JSON), 2 unknown command. There are no per-status codes — an HTTP error prints HTTP <status>: <message> (<CODE>) on stderr; branch on the code.
