Skip to main content

inkbox

Send and receive emails and phone calls via Inkbox agent identities. Use when the user wants to check inbox messages, list unread email, view a thread, search mailbox contents, draft/send an email, place an outbound phone call, list call history, retrieve call transcripts, manage vault credentials, or create/set up an Inkbox identity.

Jump to install

Source facts

Repository
LeoYeAI/openclaw-master-skills
Last source activity
July 20, 2026 at 02:05
Detected SKILL.md language
English
Stars
2,128
Forks
321

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
inkbox
description
Send and receive emails and phone calls via Inkbox agent identities. Use when the user wants to check inbox messages, list unread email, view a thread, search mailbox contents, draft/send an email, place an outbound phone call, list call history, retrieve call transcripts, manage vault credentials, or create/set up an Inkbox identity.
metadata
{"openclaw":{"emoji":"๐Ÿ“ฌ","homepage":"https://inkbox.ai","requires":{"env":"[Truncated]","bins":"[Truncated]"},"primaryEnv":"INKBOX_API_KEY"}}
# Inkbox Skill API-first communication infrastructure for AI agents โ€” email, phone, encrypted vault, and identities. ## Requirements - `INKBOX_API_KEY` โ€” Inkbox API key - `node` on `PATH` (Node.js 18+) - `INKBOX_AGENT_HANDLE` is optional; use it when already configured, otherwise ask the user which identity handle to use or create ## Runtime setup Do not assume `@inkbox/sdk` is already installed in the skill folder. When the SDK is missing, prefer a **temporary disposable Node directory** over modifying the workspace or skill folder. Use a flow like: 1. Create a temporary directory 2. Run `npm init -y` 3. Run `npm install @inkbox/sdk` 4. Write a small `.mjs` script there 5. Run it with `node` Only install dependencies into the skill folder or workspace if the user explicitly asks. Use `.mjs` scripts with standard ESM imports. Avoid relying on `tsx --eval` or top-level-await snippets that may be runtime-fragile. ## Install & Init ```bash npm install @inkbox/sdk ``` Requires Node.js โ‰ฅ 18. ESM module โ€” no context manager needed: ```js import { Inkbox } from "@inkbox/sdk"; const inkbox = new Inkbox({ apiKey: process.env.INKBOX_API_KEY }); ``` Constructor options: `{ apiKey: string, baseUrl?: string, timeoutMs?: number }` ## Core Model ``` Inkbox (org-level client) โ”œโ”€โ”€ .createIdentity(handle) โ†’ Promise<AgentIdentity> โ”œโ”€โ”€ .getIdentity(handle) โ†’ Promise<AgentIdentity> โ”œโ”€โ”€ .listIdentities() โ†’ Promise<AgentIdentitySummary[]> โ”œโ”€โ”€ .mailboxes โ†’ MailboxesResource โ”œโ”€โ”€ .phoneNumbers โ†’ PhoneNumbersResource โ”œโ”€โ”€ .vault โ†’ VaultResource โ””โ”€โ”€ .createSigningKey() โ†’ Promise<SigningKey> AgentIdentity (identity-scoped helper) โ”œโ”€โ”€ .mailbox โ†’ IdentityMailbox | null โ”œโ”€โ”€ .phoneNumber โ†’ IdentityPhoneNumber | null โ”œโ”€โ”€ .getCredentials() โ†’ Promise<Credentials> (requires vault unlocked) โ”œโ”€โ”€ mail methods (requires assigned mailbox) โ””โ”€โ”€ phone methods (requires assigned phone number) ``` An identity must have a channel assigned before you can use mail/phone methods. If not assigned, an `InkboxAPIError` is thrown. ## Identities ```js const identity = await inkbox.createIdentity("sales-agent"); const identity = await inkbox.getIdentity("sales-agent"); const identities = await inkbox.listIdentities(); // AgentIdentitySummary[] await identity.update({ newHandle: "new-name" }); // rename await identity.update({ status: "paused" }); // or "active" await identity.refresh(); // re-fetch from API, updates cached channels await identity.delete(); // unlinks channels ``` If `INKBOX_AGENT_HANDLE` is not configured, ask the user for the handle to use. After creating a new identity: - show the handle and mailbox address to the user - ask whether they want to save the handle in `skills.entries.<skill>.env.INKBOX_AGENT_HANDLE` - do not store the API key in plaintext config; prefer `skills.entries.<skill>.apiKey` with a SecretRef to `INKBOX_API_KEY` ## Channel Management ```js // Create and auto-link new channels const mailbox = await identity.createMailbox({ displayName: "Sales Agent" }); const phone = await identity.provisionPhoneNumber({ type: "toll_free" }); // or type: "local", state: "NY" console.log(mailbox.emailAddress); // e.g. "abc-xyz@inkboxmail.com" console.log(phone.number); // e.g. "+18005551234" // Link existing channels await identity.assignMailbox("mailbox-uuid"); await identity.assignPhoneNumber("phone-number-uuid"); // Unlink without deleting await identity.unlinkMailbox(); await identity.unlinkPhoneNumber(); ``` ## Mail ### Send Before sending, confirm recipients, subject, and body with the user. ```js const sent = await identity.sendEmail({ to: ["user@example.com"], subject: "Hello", bodyText: "Hi there!", // plain text (optional) bodyHtml: "<p>Hi there!</p>", // HTML (optional) cc: ["cc@example.com"], // optional bcc: ["bcc@example.com"], // optional inReplyToMessageId: sent.id, // for threaded replies attachments: [{ // optional filename: "report.pdf", contentType: "application/pdf", contentBase64: "<base64>", }], }); ``` ### Read ```js // Iterate all messages โ€” auto-paginated async generator for await (const msg of identity.iterEmails()) { console.log(msg.subject, msg.fromAddress, msg.isRead); } // Filter by direction for await (const msg of identity.iterEmails({ direction: "inbound" })) { // or "outbound" ... } // Unread only (client-side filtered) for await (const msg of identity.iterUnreadEmails()) { ... } // Mark as read const ids = []; for await (const msg of identity.iterUnreadEmails()) ids.push(msg.id); await identity.markEmailsRead(ids); // Get full thread (oldest-first) const thread = await identity.getThread(msg.threadId); for (const m of thread.messages) { console.log(`[${m.fromAddress}] ${m.subject}`); } ``` ### Search ```js // Org-level mailbox search const results = await inkbox.mailboxes.search(identity.mailbox.emailAddress, { q: "invoice", limit: 20, }); ``` This operation requires the identity to already have a mailbox provisioned. ## Phone ```js // Place outbound call โ€” stream audio via WebSocket const call = await identity.placeCall({ toNumber: "+15167251294", clientWebsocketUrl: "wss://your-agent.example.com/ws", }); console.log(call.status); console.log(call.rateLimit.callsRemaining); // rolling 24h budget // List calls (offset pagination) const calls = await identity.listCalls({ limit: 10, offset: 0 }); for (const c of calls) { console.log(c.id, c.direction, c.remotePhoneNumber, c.status); } // Transcript segments (ordered by seq) const segments = await identity.listTranscripts(calls[0].id); for (const t of segments) { console.log(`[${t.party}] ${t.text}`); // party: "local" or "remote" } ``` Always confirm before placing a call. ## Vault Encrypted credential vault with client-side Argon2id key derivation and AES-256-GCM encryption. The server never sees plaintext secrets. Requires `hash-wasm` (included as a dependency). ### Unlock & Read ```js import type { LoginPayload, APIKeyPayload, SSHKeyPayload, OtherPayload } from "@inkbox/sdk"; // Unlock with a vault key โ€” derives key via Argon2id, decrypts all secrets const unlocked = await inkbox.vault.unlock("my-Vault-key-01!"); // Optionally filter to secrets an agent identity has access to const unlocked = await inkbox.vault.unlock("my-Vault-key-01!", { identityId: "agent-uuid" }); // All decrypted secrets from the unlock bundle for (const secret of unlocked.secrets) { console.log(secret.name, secret.secretType); console.log(secret.payload); // LoginPayload, APIKeyPayload, SSHKeyPayload, or OtherPayload } // Fetch and decrypt a single secret by ID const secret = await unlocked.getSecret("secret-uuid"); const login = secret.payload as LoginPayload; console.log(login.username, login.password); ``` ### Create & Update ```js // Create a login secret (secretType inferred from payload shape) await unlocked.createSecret({ name: "AWS Production", description: "Production IAM user", payload: { password: "s3cret", username: "admin", url: "https://aws.amazon.com" }, }); // Create an API key secret await unlocked.createSecret({ name: "GitHub PAT", payload: { apiKey: "ghp_xxx" }, }); // Create an SSH key secret await unlocked.createSecret({ name: "Deploy Key", payload: { privateKey: "-----BEGIN OPENSSH PRIVATE KEY-----..." }, }); // Create a freeform secret await unlocked.createSecret({ name: "Misc", payload: { data: "any freeform content" }, }); // Update name/description and/or re-encrypt payload await unlocked.updateSecret("secret-uuid", { name: "New Name" }); await unlocked.updateSecret("secret-uuid", { payload: { password: "new", username: "new" }, }); // Delete await unlocked.deleteSecret("secret-uuid"); ``` ### Metadata (no unlock needed) ```js const info = await inkbox.vault.info(); // VaultInfo const keys = await inkbox.vault.listKeys(); // VaultKey[] const keys = await inkbox.vault.listKeys({ keyType: "recovery" }); // filter by type const secrets = await inkbox.vault.listSecrets(); // VaultSecret[] (metadata only) const secrets = await inkbox.vault.listSecrets({ secretType: "login" }); // filter by type await inkbox.vault.deleteSecret("secret-uuid"); // delete without unlocking ``` ### Payload Types | Type | Interface | Fields | |------|-----------|--------| | `login` | `LoginPayload` | `password`, `username?`, `email?`, `url?`, `notes?`, `totp?` | | `api_key` | `APIKeyPayload` | `apiKey`, `endpoint?`, `notes?` | | `key_pair` | `KeyPairPayload` | `accessKey`, `secretKey`, `endpoint?`, `notes?` | | `ssh_key` | `SSHKeyPayload` | `privateKey`, `publicKey?`, `fingerprint?`, `passphrase?`, `notes?` | | `other` | `OtherPayload` | `data` | `secretType` is immutable after creation. To change it, delete and recreate. ### Agent Credentials (identity-scoped) Agent-facing credential access โ€” typed, identity-scoped. The vault stays as the admin surface; `identity.getCredentials()` is the agent runtime surface. ```js import type { Credentials } from "@inkbox/sdk"; // Unlock the vault first (stores state on the client) await inkbox.vault.unlock("my-Vault-key-01!"); const identity = await inkbox.getIdentity("support-bot"); const creds = await identity.getCredentials(); // Discovery โ€” returns DecryptedVaultSecret[] with name/metadata const allCreds = creds.list(); const logins = creds.listLogins(); const apiKeys = creds.listApiKeys(); const sshKeys = creds.listSshKeys(); // Access by UUID โ€” returns typed payload directly const login = creds.getLogin("secret-uuid"); // โ†’ LoginPayload const apiKey = creds.getApiKey("secret-uuid"); // โ†’ APIKeyPayload const sshKey = creds.getSshKey("secret-uuid"); // โ†’ SSHKeyPayload // Generic access โ€” returns DecryptedVaultSecret const secret = creds.get("secret-uuid"); ``` - Requires `inkbox.vault.unlock()` first โ€” throws `InkboxAPIError` if vault is not unlocked - Results are filtered to secrets the identity has access to (via access rules) - Cached after first call; call `identity.refresh()` to clear the cache - `get*` throws `Error` if not found, `TypeError` if wrong secret type ### One-Time Passwords (TOTP) TOTP secrets are stored inside `LoginPayload.totp` in the encrypted vault. Codes are generated client-side โ€” no server call needed. #### From an agent identity (recommended) ```js import { parseTotpUri } from "@inkbox/sdk"; import type { LoginPayload } from "@inkbox/sdk"; // Create a login with TOTP const secret = await identity.createSecret({ name: "GitHub", payload: { username: "user@example.com", password: "s3cret", totp: parseTotpUri("otpauth://totp/GitHub:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub"), } satisfies LoginPayload, }); // Generate TOTP code const code = await identity.getTotpCode(secret.id); console.log(code.code); // e.g. "482901" console.log(code.secondsRemaining); // e.g. 17 // Add/replace TOTP on existing login await identity.setTotp(secretId, "otpauth://totp/...?secret=..."); // Remove TOTP await identity.removeTotp(secretId); ``` #### From the unlocked vault (org-level) ```js const unlocked = await inkbox.vault.unlock("my-Vault-key-01!"); // Same methods available on UnlockedVault await unlocked.setTotp(secretId, totpConfigOrUri); await unlocked.removeTotp(secretId); const code = await unlocked.getTotpCode(secretId); ``` #### TOTPCode fields | Field | Type | Description | |---|---|---| | `code` | `string` | The OTP code (e.g. `"482901"`) | | `periodStart` | `number` | Unix timestamp when the code became valid |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub