.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.development
Search the web for current information, news, facts, and answers. Use when asked questions about current events, needing to look something up, finding websites, researching topics, or when you need up-to-date information beyond your training data.
development
Summarize any URL, YouTube video, podcast, PDF, or file into concise text. Use when asked to read an article, summarize a link, get the gist of a video or podcast, extract content from a URL, or when you need to understand what a web page or document contains.
development
Play music via Spotify and control Google Home speakers. Use when asked to play music, songs, artists, playlists, podcasts, or control speakers/volume/audio.
testing
Create new OpenClaw skills, modify and improve existing skills, and measure skill performance with evals. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Also use when asked to "make a skill", "turn this into a skill", "improve this skill", or "test this skill".