.claude/skills/bluebubbles-attachment-send/SKILL.md
Send file attachments (audio, images, documents) via BlueBubbles iMessage API. Use when: (1) Need to send an MP3, image, or file via iMessage through BlueBubbles, (2) "Chat does not exist!" error when sending attachments via curl -F (semicolons in chatGuid get mangled by curl multipart form parsing), (3) /api/v1/message/text endpoint ignores the attachment field in multipart mode, (4) Building TTS audio briefings or voice messages for iMessage delivery, (5) Need to know the correct BB API endpoint for attachments (/api/v1/message/attachment). Covers the correct endpoint, required fields, and the curl semicolon bug workaround.
npx skillsauth add Dbochman/dotfiles bluebubbles-attachment-sendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Sending file attachments (audio, images, etc.) via BlueBubbles iMessage API is not straightforward. The text endpoint ignores file uploads, and curl's multipart form handling breaks chat GUIDs containing semicolons.
POST /api/v1/message/text with multipart form data silently drops the attachmentcurl -F "chatGuid=any;-;[email protected]" returns "Chat does not exist!" (semicolons parsed as form-data separators)Use POST /api/v1/message/attachment (NOT /message/text).
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| chatGuid | string | Yes | BB chat GUID (e.g., any;-;+1XXXXXXXXXX) |
| name | string | Yes | Filename (e.g., briefing.mp3) — API returns 400 without this |
| attachment | file | Yes | The file to send (multipart upload) |
| method | string | No | private-api (recommended) or apple-script |
| tempGuid | string | No | UUID for dedup |
curl's -F flag treats semicolons as field separators, corrupting chatGuid values
like any;-;[email protected]. This causes "Chat does not exist!" errors even though the
chat exists.
Python requests handles this correctly:
import requests, uuid
pw = "your_bb_password"
url = f"http://localhost:1234/api/v1/message/attachment?password={pw}"
files = {"attachment": ("briefing.mp3", open("/tmp/briefing.mp3", "rb"), "audio/mpeg")}
data = {
"chatGuid": "any;-;+1XXXXXXXXXX",
"tempGuid": str(uuid.uuid4()).upper(),
"name": "briefing.mp3",
"method": "private-api",
}
r = requests.post(url, data=data, files=files, timeout=60)
print(r.json()) # {"status": 200, "message": "Attachment sent!"}
~/.openclaw/bin/send-audio-briefing wraps TTS generation + BB attachment send:
# Generate TTS + send audio + send summary text
send-audio-briefing "any;-;+1XXXXXXXXXX" "Hello Julia!" -m "Short summary" -v Sarah
| Endpoint | Accepts Files? | Auth |
|----------|---------------|------|
| /message/text (JSON body) | No | ?password= query param |
| /message/text (multipart) | Silently ignores file fields | ?password= query param |
| /message/attachment (multipart) | Yes — this is the correct one | ?password= query param |
{"status": 200, "message": "Attachment sent!"}data.attachments array should contain the file metadata# Full workflow: generate TTS audio, send as iMessage attachment, follow up with text
import requests, uuid, subprocess, os
# 1. Generate audio
subprocess.run(["sag-wrapper", "-o", "/tmp/briefing.mp3", "--play=false", "-v", "Sarah",
"Good morning! You have 2 meetings today."])
# 2. Send audio attachment
pw = os.environ["BLUEBUBBLES_PASSWORD"]
files = {"attachment": ("morning-briefing.mp3", open("/tmp/briefing.mp3", "rb"), "audio/mpeg")}
data = {"chatGuid": "any;-;+1XXXXXXXXXX", "name": "morning-briefing.mp3",
"tempGuid": str(uuid.uuid4()).upper(), "method": "private-api"}
r = requests.post(f"http://localhost:1234/api/v1/message/attachment?password={pw}",
data=data, files=files, timeout=60)
# 3. Send summary text
body = {"chatGuid": "any;-;+1XXXXXXXXXX", "message": "2 meetings today",
"tempGuid": str(uuid.uuid4()).upper(), "method": "private-api"}
requests.post(f"http://localhost:1234/api/v1/message/text?password={pw}",
json=body, timeout=15)
.caf format and the voice memo extension — that's different?password= query param (not header — header returns 401)name field is REQUIRED on /message/attachment — omitting it returns 400audio/mpeg for MP3, image/png for PNG, application/pdf for PDF, etc.tools
Use exact configured Reolink cameras through the local Home Hub for availability and power status, fresh stills, visual commentary, protected Dylan/Julia/household sharing, and reversible spotlight control. Supports trusted owner tasks and explicitly scoped proactive automations; not for Nest or Ring cameras, arbitrary recipients, recordings, account changes, or raw camera APIs.
data-ai
Privately manage Dylan and Julia's household plant inventory and care history by physical location, bed, and exact Flower Cam view. Use for confirmed plant onboarding from camera conversations, camera- or bed-filtered inventory, record corrections, individual or whole-bed care, and private filtered exports. Pair with reolink-camera when an owner asks about plants visible in Flower Cam images.
testing
Inspect and control the physically secured Reachy Mini at Crosstown through ClawBody. Use for requests to check Reachy, look around, express an emotion, play any official emotion or dance preset, speak proactively, mute or unmute its microphone, stop movement, or describe what its camera sees.
tools
Handle Reachy/iMessage handoffs, selective durable memory, forgetting, and diagnostics; automatic context comes from the gateway plugin.