بنقرة واحدة
Send, receive, and orchestrate email between sandboxes using km-send and km-recv
npx skills add https://github.com/whereiskurt/klanker-maker --skill emailانسخ والصق هذا الأمر في Claude Code لتثبيت المهارة
Send, receive, and orchestrate email between sandboxes using km-send and km-recv
npx skills add https://github.com/whereiskurt/klanker-maker --skill emailانسخ والصق هذا الأمر في Claude Code لتثبيت المهارة
Provision cross-account IAM roles in the klanker AWS account that trust k8s clusters in other AWS accounts via IRSA (projected ServiceAccount tokens, auto-rotating 3600s sessions)
Operator CLI guide for the km command — creating sandboxes, running agents, learning traffic, managing lifecycle
Post messages from inside a sandbox to its per-sandbox Slack channel using km-slack — for end-of-task status, progress notes, operator pings, threaded transcript replies, and Block-Kit-rendered output
One-time platform setup for an operator workstation — km configure, km init, multi-instance resource_prefix isolation, Slack/Lambda bootstrap, and rollout sequences after sidecar/Lambda changes
Request platform actions by emailing the operator inbox with natural language commands
Detect sandbox environment, discover email policy, verify email and Slack tooling
| name | |
| description | Send, receive, and orchestrate email between sandboxes using km-send and km-recv |
This skill provides patterns for sending, receiving, polling, and coordinating work across sandboxes via email. All email is routed through SES with Ed25519 signing and optional NaCl encryption.
Prerequisites: Run klanker:sandbox first to detect environment and email policy.
klanker:sandbox — environment + tooling detection (run first)klanker:slack — alternative channel for operator-facing notifications (one-way, low-latency)klanker:operator — to request an operator action (uses this skill under the hood)Tooling location: km-send is at
/opt/km/bin/km-send; km-recv is at/opt/km/bin/km-recv. Both are on the default sandbox PATH so the bare command names work in interactive shells. Use the absolute paths in scripts, cron jobs, and systemd units where PATH may be minimal.
Always write the body to a file first — required for reliable Ed25519 signing on OpenSSL 3.5+:
cat > /tmp/msg-body.txt << 'EOF'
Your message body here.
EOF
km-send --subject "subject line" --body /tmp/msg-body.txt
Default recipient is the operator ($KM_OPERATOR_EMAIL). To send to another sandbox:
km-send --subject "task results" --body /tmp/results.txt --to sb-x9y8z7@sandboxes.klankermaker.ai
km-send --subject "output ready" --body /tmp/summary.txt --attach /workspace/output.tar.gz
Multiple attachments: --attach file1.tar.gz,file2.json
| Flag | Default | Description |
|---|---|---|
--subject | (required) | Email subject line |
--body | stdin | File path or - for stdin |
--to | operator | Recipient email address |
--attach | Comma-separated file paths | |
--cc | Comma-separated CC recipients | |
--use-bcc | false | BCC the operator |
--reply-to | Reply-To header | |
--no-sign | false | Skip Ed25519 signing and X-KM-* headers — use ONLY for external (non-sandbox) recipients (Gmail, etc.). KM-AUTH safe-phrase auto-append to operator inbox is preserved. |
km-send automatically signs with the sandbox's Ed25519 key from SSM. You do not need to handle signing manually. If the profile has signing: required and signing fails, the send exits non-zero — this is correct, do not retry without investigating.
OpenSSL 3.5+ note: If
km-sendfails withunable to determine file size for oneshot operation / Public Key operation error, the sandbox is running an older copy of the script that pipes the body intoopenssl pkeyutl -sign -rawinvia stdin. OpenSSL 3.5 (Jan 2026) requires a seekable input. The platform script has been fixed to pass-in "$BODY_TMP"explicitly. For a sandbox provisioned before this fix, patch in place:sudo sed -i 's|-rawin < "\$BODY_TMP"|-rawin -in "\$BODY_TMP"|' /opt/km/bin/km-sendReport it to the operator so it can be applied upstream.
For non-sandbox recipients (Gmail, corporate email, etc.) the X-KM-Sender-ID and X-KM-Signature headers are not honored by external receivers and may even degrade deliverability. Use --no-sign to skip the SSM key fetch, skip the openssl signing step, and omit the X-KM-* headers entirely:
cat > /tmp/external-msg.txt << 'EOF'
Hi from sandbox.
EOF
km-send --no-sign --to user@gmail.com --subject "hello from sandbox" --body /tmp/external-msg.txt
Behavior under --no-sign:
aws ssm get-parameter for the signing key is skipped (faster startup; works even if the role lost SSM read).openssl pkeyutl -sign is skipped.X-KM-Sender-ID and NO X-KM-Signature headers.Use --no-sign ONLY for genuine external recipients. For sandbox-to-sandbox messaging, omit --no-sign so signature verification works on the receiving sandbox.
Replies from external addresses: When a Gmail user replies to your --no-sign send, the reply must contain KM-AUTH: <safe-phrase> somewhere in the body to pass the inbound km-mail-poller filter. The operator configures the safe phrase via km configure; check with the operator before instructing an external user to reply.
--body <file>, never pipe to stdin for production messageskm-send — non-zero means the message was not sentRe:)Always use --json for machine-parseable output:
km-recv --json
JSON output per message:
{
"index": 1,
"from": "sb-a1b2c3d4@sandboxes.klankermaker.ai",
"sender_id": "sb-a1b2c3d4",
"to": "sb-x9y8z7w6@sandboxes.klankermaker.ai",
"subject": "task results",
"signature": "OK",
"encrypted": false,
"external": false,
"body": "message body text",
"attachments": ["output.tar.gz"]
}
external is true when the message lacks an X-KM-Sender-ID header (i.e., it came from a non-sandbox sender via the safe-phrase gate). The human-readable km-recv output also appends [EXTERNAL] to the From column for these messages.signature will be "—" (em-dash; "unsigned") for external: true messages. They passed the safe-phrase gate but have no cryptographic verification.Check the signature field on every received message:
| Value | Meaning | Action |
|---|---|---|
OK | Valid Ed25519 signature | Trust the message |
FAIL | Signature did not verify | Do not trust. If verifyInbound: required, reject. |
? | Sender has no published key | Treat as untrusted |
— | Message was not signed | If verifyInbound: required, reject. |
— + external: true | External sender; passed safe-phrase gate but no cryptographic verification | Treat as authenticated by KM-AUTH only — do not extend trust beyond the safe phrase's scope. |
After processing a message, mark it to avoid re-processing:
km-recv --json --mark-read
Messages move from /var/mail/km/new/ to /var/mail/km/processed/.
For real-time monitoring (polls every 5 seconds):
km-recv --watch
Note: --watch is for human monitoring. For automated workflows, use the poll-and-wait pattern below.
Send a message and wait for a reply. This is the core pattern for request/response workflows.
# 1. Send the request
cat > /tmp/task-request.txt << 'EOF'
Please run the test suite and report results.
EOF
km-send --subject "test-run-request" --body /tmp/task-request.txt --to $TARGET_SANDBOX
# 2. Poll for reply with backoff
TIMEOUT=300 # 5 minutes
INTERVAL=10 # start at 10 seconds
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
REPLY=$(km-recv --json 2>/dev/null | jq -r 'select(.subject | test("Re:.*test-run-request")) | .body' 2>/dev/null)
if [ -n "$REPLY" ]; then
echo "Got reply: $REPLY"
km-recv --mark-read
break
fi
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
# Backoff: increase interval up to 60s
[ $INTERVAL -lt 60 ] && INTERVAL=$((INTERVAL + 5))
done
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timeout waiting for reply after ${TIMEOUT}s"
fi
km-mail-poller syncs S3 to /var/mail/km/new/ every 60 secondsSend a task to another sandbox and wait for the result:
# 1. Compose task
CORRELATION_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)
cat > /tmp/task.json << EOF
{
"action": "task-assign",
"correlation_id": "$CORRELATION_ID",
"sender": "$KM_SANDBOX_ID",
"task": "Implement the authentication module",
"repo": "https://github.com/org/project.git",
"branch": "feature/auth",
"timeout": "30m"
}
EOF
km-send --subject "km-agent:task-assign:$CORRELATION_ID" \
--body /tmp/task.json \
--to $WORKER_SANDBOX_EMAIL
# 2. Wait for result (match by correlation ID in subject)
# ... use poll-and-wait pattern above, matching on $CORRELATION_ID
For agent-to-agent communication, use the km-agent:{action}:{correlation-id} subject format:
| Action | Direction | Purpose |
|---|---|---|
task-assign | requester -> worker | Assign a subtask |
task-result | worker -> requester | Report completion with results |
review-request | worker -> reviewer | Request code review |
review-response | reviewer -> worker | Approve or reject |
status-query | any -> any | Request current status |
status-response | any -> any | Report current status |
abort | requester -> worker | Cancel in-progress task |
Send the same task to multiple sandboxes and collect results:
CORRELATION_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)
WORKERS=("sb-w1@sandboxes.klankermaker.ai" "sb-w2@sandboxes.klankermaker.ai" "sb-w3@sandboxes.klankermaker.ai")
EXPECTED=${#WORKERS[@]}
# Send to all workers
for WORKER in "${WORKERS[@]}"; do
km-send --subject "km-agent:task-assign:$CORRELATION_ID" \
--body /tmp/task.json --to "$WORKER"
done
# Collect results
RECEIVED=0
TIMEOUT=600
ELAPSED=0
while [ $RECEIVED -lt $EXPECTED ] && [ $ELAPSED -lt $TIMEOUT ]; do
RESULTS=$(km-recv --json 2>/dev/null | jq -c "select(.subject | test(\"$CORRELATION_ID\"))")
RECEIVED=$(echo "$RESULTS" | grep -c . 2>/dev/null || echo 0)
[ $RECEIVED -ge $EXPECTED ] && break
sleep 15
ELAPSED=$((ELAPSED + 15))
done
echo "Received $RECEIVED of $EXPECTED results"
| Situation | Action |
|---|---|
km-send exits non-zero | Do not retry blindly. Check: is the signing key accessible? Is SES reachable? Is the recipient address valid? |
Signature FAIL on received message | Log a warning. If verifyInbound: required, do not process the message body. |
Signature ? (no key found) | Sender may be new or key not yet published. Treat as untrusted. |
| Poll timeout | The recipient may be stopped, crashed, or overloaded. Offer to: resend, check sandbox status via operator, or give up. |
| Empty inbox after long wait | Check if km-mail-poller is running: systemctl status km-mail-poller. If dead, emails are stuck in S3. |
| Attachment too large | SES has a 10MB raw message limit. For large files, upload to S3 and send the S3 key in the body instead. |
| External email not appearing in inbox | The sender's message is missing the KM-AUTH: <phrase> body line. Ask the operator for the safe phrase; instruct the external sender to include it on a single line in the message body. |
--no-sign + sandbox recipient | Don't. Sandbox-to-sandbox messages should always be signed so the receiver's verifyInbound policy can validate them. |