Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Check Facebook page inbox messages via Meta Business Suite browser automation. Use when asked to check Facebook messages, reply to FB customers, or manage Facebook page inbox. Supports multiple pages with custom aliases.
.claude/skills/meta-fb-inbox/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
⚠️ CRITICAL: Always use profile:"openclaw" (isolated browser) for all browser actions. Never use Chrome relay. Keep the same targetId across operations to avoid losing the tab.
⚠️ CONTEXT Management:
snapshot refs:"aria" compact:true to find element refs, then act + click/type with ref.Before first use, check if config.json exists:
read file_path:"skills/meta-fb-inbox/config.json"⚠️ PATH RULE: Always use workspace-relative paths starting from skills/meta-fb-inbox/. Never use ../ or absolute paths.
If missing or empty, help the user run the setup wizard:
bashcd skills/meta-fb-inbox node scripts/setup.js
config.json contains a pages array. Each page has an alias (display name) and url (Meta Business Suite inbox URL):
json{ "pages": [ { "alias": "fb fanpage", "url": "https://business.facebook.com/latest/inbox/all/?&asset_id=123456789012345" }, { "alias": "fb fanpage 2", "url": "https://business.facebook.com/latest/inbox/all/?&asset_id=987654321098765" } ] }
When the user asks to check messages:
config.json → pages.This is the first step for all operations below.
browser action:"status" If "running": false, start the browser service: browser action:"start" profile:"openclaw" Wait 2-3 seconds for the service to initialize.
read file_path:"skills/meta-fb-inbox/config.json" Resolve the page alias to a URL (see "Resolving a Page" above).
browser action:"open" profile:"openclaw" targetUrl:"<pageUrl>" Capture targetId and url from response.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":3000}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"evaluate","fn":"function(){return window.location.href;}"}
business.facebook.com and shows the inbox → logged in, proceed.a. Open a blank tab: browser action:"open" profile:"openclaw" targetUrl:"about:blank"
b. Get all tabs: browser action:"tabs" profile:"openclaw"
c. Close all tabs except the newest one (about:blank): browser action:"close" profile:"openclaw" targetId:"<your_targetId>"
Complete flow: open page → screenshot → report.
read file_path:"skills/meta-fb-inbox/config.json" → resolve page URLbrowser action:"start" profile:"openclaw"browser action:"open" profile:"openclaw" targetUrl:<pageUrl> → get targetIdbrowser action:"act" profile:"openclaw" targetId:<targetId> request:{"kind":"wait","timeMs":4000}browser action:"snapshot" profile:"openclaw" targetId:<targetId> refs:"aria" compact:true<name> (<time>) <preview> [未讀/已讀]If the snapshot is hard to parse (Meta's DOM is complex), fall back to:
browser action:"screenshot" profile:"openclaw" targetId:<targetId>Then describe what you see in the screenshot.
Why get URLs? Having the direct conversation URL lets you jump straight to a chat later without searching for it again. Saves time when replying.
When to get URLs: After listing chats, get URLs for unread conversations or any conversation you might need to access again.
📝 Simple 3-Step Method:
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"evaluate","fn":"function() { const containers = document.querySelectorAll('div[style*=\"position: absolute\"]'); for (let container of containers) { const nameEl = container.querySelector('div.x1vvvo52.x1fvot60.xxio538'); if (nameEl && nameEl.textContent.trim() === '<customer_name>') { nameEl.click(); return {clicked: true}; } } return {error: 'not found'}; }"} Replace <customer_name> with the actual customer name from your conversation list.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":2000}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"evaluate","fn":"function(){return window.location.href;}"}
✅ Example URL Format:
https://business.facebook.com/latest/inbox/all/?&asset_id=123456789012345&selected_item_id=1234567890&thread_type=FB_MESSAGEOr with optional mailbox_id:
https://business.facebook.com/latest/inbox/all/?&asset_id=123456789012345&mailbox_id=123456789012345&selected_item_id=9876543210&thread_type=FB_MESSAGE🔍 Understanding the URL:
asset_id = Your Facebook page ID (stays the same)selected_item_id = Unique conversation ID (different for each customer)thread_type=FB_MESSAGE = Messenger conversation typemailbox_id = Optional, may appear for some conversations💾 How to Use Later:
Next time you need to access this conversation, skip all the searching:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<saved_conversation_url>"Wait 2 seconds, and you're in the conversation!
💡 Tip: Get URLs for unread messages during your check routine, store them in a file or memory, and reuse them for instant access.
🔄 Getting Multiple URLs:
If you need URLs for multiple conversations:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<pageUrl>" Wait 2 seconds.
Alternative method (when already in conversation): If you're already viewing a conversation and just need its URL, skip step 1 and directly run step 3 - the URL is already there!
⚡ Fast Path: If you have the conversation URL from "Get URLs for Conversations":
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<conversation_url>"Wait 2 seconds, then skip to step 3.
Standard Path:
To programmatically read messages:
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"evaluate","fn":"<contents_of_scripts/read-messages.js>"} Read the script file: read file_path:"skills/meta-fb-inbox/scripts/read-messages.js" And pass its entire contents as the fn parameter (wrapped in parentheses).
The script returns an array of message objects: json [ {"text": "你好", "isCustomer": true, "hasImage": false, "imageUrl": null}, {"text": "[Image]", "isCustomer": true, "hasImage": true, "imageUrl": "https://scontent-..."} ]
⚠️ Default download location: ~/Downloads (do NOT clutter the workspace).
For each message with hasImage: true: bash cd ~/Downloads curl -O "<imageUrl>"
The downloaded file will use the original filename from the URL.
If you want a custom filename: bash cd ~/Downloads curl -o "fb-message-$(date +%Y%m%d-%H%M%S).jpg" "<imageUrl>"
⚡ Fast Path (if you have conversation URL):
If you already obtained the conversation URL from "Get URLs for Conversations" section, skip step 1 and go directly:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<conversation_url>"Wait 2 seconds, then proceed to step 2 (Take snapshot to find input box).
Standard Path (when you don't have the URL):
Click the customer name in the conversation list: browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"evaluate","fn":"function() { const containers = document.querySelectorAll('div[style*=\"position: absolute\"]'); for (let container of containers) { const nameEl = container.querySelector('div.x1vvvo52.x1fvot60.xxio538'); if (nameEl && nameEl.textContent.trim() === '<customer_name>') { nameEl.click(); return {clicked: true}; } } return {error: 'not found'}; }"} Replace <customer_name> with the actual customer name.
Wait 2 seconds for the conversation to load.
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for textbox with [active] attribute.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"type","ref":"<input_ref>","text":"<your_message>"} After typing, wait 1 second for the send button to appear.
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for a new button near the textbox. The send button appears after you type text (it replaces the "like" button).
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<send_button_ref>"}
Wait 2 seconds, then take a screenshot or check the conversation list to confirm the message appears.
⚡ Fast Path: If you have the conversation URL, navigate directly:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<conversation_url>"Wait 2 seconds, then proceed to the label operations.
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true
Look for the Labels section in the right sidebar (appears before Notes section):
heading with level=3 (the Labels heading)button elementbutton with text "clearLabel" browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<clearLabel_ref>"}
Example from snapshot:
Click the clearLabel button (ref=e952) to remove the label.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":1500}
Take a screenshot or snapshot to confirm the label is gone.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"type","ref":"<combobox_ref>","text":"<label_name>","submit":true}
⚡ Fast Path: If you have the conversation URL, navigate directly:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<conversation_url>"Wait 2 seconds, then proceed to the note operations.
Question: Do you need to ADD or EDIT a note?
→ Contact has NO note → Use "Add a New Note" section below.
→ Contact has existing note → Use "Edit an Existing Note" section below.
Simple rule:
How to identify in snapshot:
button [ref=eXXX] or link [ref=eXXX] with NO paragraph text nearbylink [ref=eXXX] (edit) + link [ref=eYYY] (delete) + paragraph [ref=eZZZ]: "note text"The Notes section is located below the Labels section in the right sidebar. There are two states:
paragraph element)Best Practice: Keep only one note per contact. Use "Edit" to update existing notes rather than adding multiple notes.
In snapshot, look for these patterns:
No note:
- heading [level=3] [ref=eXXX] ← Notes heading
- button [ref=eYYY] ← Single "add note" buttonHas note:
- heading [level=3] [ref=eXXX] ← Notes heading
- link [ref=eYYY] ← "add note" link
- text: "X minutes ago ·" ← Timestamp
- link [ref=eZZZ] ← Edit link (THIS ONE for editing!)
- text: "·"
- link [ref=eAAA] ← Delete link
- paragraph [ref=eBBB]: "note text" ← Existing note contentWhen to use: Contact already has a note. You will see an Edit link next to existing note text with timestamp.
Step-by-step:
Wait 2 seconds for it to load.
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for the pattern (Notes section):
Key: Find the link that appears AFTER the timestamp text and BEFORE the "·" separator. That's your Edit button.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<edit_button_ref>"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":1500}
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for:
Key: The textbox will have the current note text. The Save button will show [disabled] attribute.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<textbox_ref>"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"press","key":"Meta+a"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"type","ref":"<textbox_ref>","text":"Customer ID: 12345 ✅"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":500}
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for:
Key: The Save button will now show [cursor=pointer] and will NOT have [disabled] attribute.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<save_button_ref>"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":2000} Take screenshot to confirm update.
Common mistakes to avoid:
When to use: Contact has NO existing note. You will see ONLY a single button/link (no existing note text).
Step-by-step:
Wait 2 seconds for it to load.
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for the pattern (Notes section with NO existing note):
Key: You should see ONLY one button/link under the Notes heading, with NO paragraph element containing note text.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<button_ref>"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":1500}
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for:
/placeholder: ... ← Placeholder text
Key: The textbox might be labeled "Hidden Label" or similar. Look for [disabled] attribute on the Save button.
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"type","ref":"<textbox_ref>","text":"Customer note text here"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":500}
browser action:"snapshot" profile:"openclaw" targetId:"<targetId>" refs:"aria" compact:true Look for:
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"click","ref":"<save_button_ref>"}
browser action:"act" profile:"openclaw" targetId:"<targetId>" request:{"kind":"wait","timeMs":2000} Take a screenshot to confirm the note appears in the sidebar.
Common mistakes to avoid:
In snapshot:
- link [ref=eXXX] ← Edit link
- text: "·"
- link [ref=eYYY] ← Delete link (THIS ONE!)If multiple pages are configured:
browser action:"navigate" profile:"openclaw" targetId:"<targetId>" targetUrl:"<newPageUrl>"
https://business.facebook.com/latest/inbox/all/?&asset_id=<PAGE_ID>| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +59 percentage points is the difference between those two pass rates over the 19 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.