What this covers
With theautomations:write and inbox:write scopes, a connected AI client (Claude Desktop, Cursor, Claude Code, a custom agent) can do everything the Outreach builder and the inbox composer can, including dropping screenshots and product images into the emails it writes. This page explains the three things a client needs to get right:
- Content is markdown-first. Write bodies in markdown; Halo renders the HTML with the same converter the dashboard editor uses, so a human can keep editing what the AI drafted.
- Images go through
media_upload_image. Upload once, get a public URL, reference it with. - Launching is a handshake. Readiness, test send, audience preview, then activate with the previewed count.
All of this is exposed as ordinary MCP tools. If you connect Claude and simply say “build me a 4-step onboarding series for new Pro users, with a screenshot of the integrations page in email 2”, the tools below are what Claude calls under the hood. You do not need to call them by hand.
The content model
Every email, in-app message and banner step stores two fields:
Always write
body_markdown. The server derives body_html with the composer’s own converter, so the step opens in the builder fully editable: images render as image blocks, buttons as button blocks, merge tags as chips. If a client writes only HTML the step still sends, but the builder shows it as plain text and loses the rich blocks on the next human edit.
Supported markdown:
The same converter is used by
inbox_reply_ticket_email (body_markdown) and inbox_message_customer (content_format: "markdown"). Chat replies render markdown natively in the widget, including images.
Personalisation (merge tags)
Subjects and bodies can carry merge tags that are filled in per recipient at send time. The client does not need to guess which ones exist:automation_list_merge_fields returns the full catalogue for the workspace, including custom traits.
Syntax is
{{field}} or {{field|fallback}}; the fallback is used when the recipient has no value. Always give first_name and name a fallback such as |there.
Two safety nets catch mistakes before anything sends:
automation_upsert_stepreturns amerge_tagssummary for the step, with a warning for every tag that will not resolve (a typo like{{frist_name}}, a composer-only field such as{{plan}}that lives on the company, a Stripe tag, or a greeting with no fallback).automation_readinessrepeats those warnings across every email step of the outreach.
inbox_reply_ticket_email, inbox_reply_ticket_chat and inbox_message_customer refuse bodies that contain outreach-style tags so a customer never receives literal braces; the client should write the customer’s real name from data_get_ticket instead.
Adding images
Email bodies cannot carry inlinedata: images (mail clients and Halo’s sanitizer both refuse them), so an image has to live at a public URL first. Two tools handle that:
media_upload_image
Pass either data_base64 (the raw PNG / JPEG / GIF / WebP bytes) or source_url (a public https URL Halo downloads for you). Optional alt and file_name.
markdown on its own line in the body. Images land in the same storage folder the dashboard composer uses, so they also appear when a teammate opens the step.
media_list_images
Lists the workspace’s previously uploaded email images, newest first, each with its URL and a ready-made markdown snippet, plus the org’s email-branding logo URL. Use it to reuse the logo or last week’s screenshot instead of uploading again.
Permissions
Both tools are available with eitherautomations:write or inbox:write, and the person who authorized the connection must hold automation:edit or inbox:reply in the dashboard, the same gate as the composer’s upload button.
Building a broadcast
1
Create
automation_create with type: "broadcast". The broadcast is seeded with one empty email step; automation_get returns its step_id.2
Pick the sender
automation_list_senders lists verified from-addresses, teammates, aliases and inbox views. Pass one of from_address_id / from_member_id / from_alias_id to automation_update, along with email_style (plain, personal, branded) and reply_to.3
Write the email
automation_upsert_step with the step_id, a subject and body_markdown. Upload any images first and paste their markdown.4
Target
automation_set_audience with all, a saved segment_id (from data_list_segments), ad-hoc filters, or a subscription_id.5
Check, test, preview
automation_readiness returns blockers and non-blocking warnings (no sender chosen, past schedule, non-https images). automation_send_test emails you a [TEST] copy (defaults to the person who authorized the connection). automation_preview_audience returns the live recipient count.6
Activate
automation_activate with expected_recipient_count set to the previewed users_count. The server re-resolves the audience and refuses on any mismatch.Building a series
The same flow, with two differences: the series starts with no steps, and the entry rule matters.1
Create and target
automation_create with type: "series", then automation_set_audience.2
Set the flow
automation_set_flow with the entry_rule (manual, user_created, event + event_name, or segment_match), optional exit_rule and goal.3
Add steps in order
Call
automation_upsert_step once per step: an email (subject + body_markdown), then a wait (delay_days: 3), then the next email, and so on. Steps append in call order; pass step_order to insert in the middle and later steps shift down. condition steps take settings: { condition_type, condition_value }.4
Review
automation_get returns every step in flow order with its markdown and the images it references. Edit any step by passing its step_id back to automation_upsert_step; only the fields you pass change.5
Launch
Readiness, test send, preview, activate, exactly as for a broadcast. Series with a
user_created or event trigger enrol contacts as they qualify and skip the recipient-count handshake.automation_duplicate clones an existing broadcast or series as a new draft, including its steps, audience, settings and canvas.
Replying in the inbox with images
inbox_reply_ticket_email takes body_markdown; inbox_reply_ticket_chat renders content as markdown in the widget. In both cases upload the screenshot with media_upload_image and paste the returned markdown:
inbox_message_customer (proactive outreach on a new thread) accepts content_format: "markdown" and renders the email channel the same way.
Tool reference
Every call, allowed or refused, lands in the
action_executions audit log. Image bytes are redacted from the audit row.
Where to go next
Scopes & Permissions
The full scope grammar and the grantor-permission gate behind every write.
Series
What waits, conditions, triggers and A/B variants do once the AI has drafted the steps.
Broadcasts
Sender options, email styles, scheduling and results.
Sending emails
Verified domains, from-addresses and how plain, personal and branded emails are wrapped at send time.