MCP server
Connect your AI tools, Claude Code, Claude Desktop, Cursor, or anything else that speaks MCP, straight to your Mara workspace. Your agent can read your programs, drafts, sends, and Playbook, and act on your behalf: draft a program, approve a send, pause something that is not working. It runs under the same guardrails as your dashboard chat.
Over MCP, Mara never sees your code. No tool on this server has an input for files, source, or repository access. What your agent sends is event names, state labels, the decisions you approve, and whatever it types into a question.
This page is the complete spec, written for humans and for AI coding agents. The raw markdown lives at hiremara.com/docs/mcp.md. The fastest path: open Settings, then Integrations, then MCP (AI tools) in your dashboard, generate a key, and copy the ready-made connect command.
What it is
POST /api/mcp is a remote MCP server over Streamable HTTP. Every call authenticates, resolves your workspace, runs one tool, and returns. There is no separate session to manage; each request carries your key and stands on its own.
Authentication
Auth is a per-workspace key with the prefix mara_mcp_, generated from Settings, Integrations, MCP (AI tools). The raw key is shown exactly once at generation; Mara stores only a hash. You can hold several live keys at once, one per machine or agent, and revoke any of them without touching the others.
Send it as a bearer token:
Authorization: Bearer mara_mcp_...
This is a credential for your own tools, not a public API. Keep it out of source control the same way you would an API key for any other service.
Connect Claude Code
claude mcp add --transport http mara https://hiremara.com/api/mcp --header "Authorization: Bearer <your-mcp-key>"
Run it once from anywhere; Claude Code remembers the connection. Your settings row's copy button fills in your real key.
Connect Claude Desktop
Add an entry to your MCP config (Settings, Developer, Edit Config in Claude Desktop):
{
"mcpServers": {
"mara": {
"url": "https://hiremara.com/api/mcp",
"headers": {
"Authorization": "Bearer <your-mcp-key>"
}
}
}
}
Restart Claude Desktop after saving.
Connect Cursor
Cursor reads the same shape. Add it under Settings, MCP:
{
"mcpServers": {
"mara": {
"url": "https://hiremara.com/api/mcp",
"headers": {
"Authorization": "Bearer <your-mcp-key>"
}
}
}
}
Set up with the mara-setup prompt
Mara ships the whole setup brief with the server, as an MCP prompt. Your client namespaces it by the name you added the server under, so in Claude Code you type:
/mara:mara-setup
Some clients list the same prompt as /mcp__mara__mara_setup instead. If you added the server under a different name than mara, use that name.
Your agent gets the ordered brief and works it: read what is already done, propose your customer journey states and the goal from your code and confirm them with you, wire up and map your events, backfill your existing users without emailing any of them, hand Mara your real product facts, ask whether your emails should sound like you or your product, write the welcome program's copy and show you a real rendered preview of it, hand you the link to start your subscription and wait, since approving a program is blocked without one, review the drafted welcome program with you before anything is approved, and land the sending domain records. It finishes by reporting what is done and what is still verifying.
The brief lives on the server, so your agent gets the current version every time it runs it, not a copy someone pasted once.
If your client does not list MCP prompts, paste this instead:
Set up my product with Mara, our lifecycle email marketer. You have Mara's MCP server connected.
Call get_setup_status first, then work through the ordered setup checklist at https://hiremara.com/docs/mcp. You read the code, Mara does not: send her event names, state labels, product facts and the decisions I approve, nothing else. Ask me before approving any program.
That checklist is "For AI coding agents" at the bottom of this page: the same steps the prompt carries, written out.
The approval promise
Nothing sends to a customer without approval in your dashboard queue, unless your own policy gate says otherwise (for example, a reply-autonomy setting you turned on yourself). Drafting tools call a model and can take up to a minute or so to return; everything else is a fast read or a direct database action. A drafted email lands pending_approval exactly the same way it would from chat: you approve it, or Mara's autonomy setting does, never the tool call by itself.
Connect your product
If you self host, or you have no CRM connected, the fastest way in is to hand your coding agent one instruction: set up the Mara API for me. Two tools close the loop end to end, in one conversation, without you ever opening Settings:
setup_events_ingestionmints your ingestion key (or reports its status if one already exists), hands your agent the exact endpoint URL, and optionally maps which event means "a new user signed up" withsignupEventType, which also retargets your seeded welcome program onto that event so it welcomes signups rather than the defaultsubscription-started. It only retargets a program that is still a draft. If you already approved your welcome program, it leaves it alone and says so, so you never end up with two.- Your agent writes the integration, using the linked spec at /docs/api.
check_recent_eventsconfirms the test event your agent fires actually landed, with a per-connector health summary, so you get a "done, verified" report instead of a "should be working" guess.
Both tools are MCP-only: they never appear in your dashboard chat, since chat is not the place for a raw ingestion key to surface.
Tools
Thirteen tools only ever read:
| Tool | What it does |
|---|---|
list_pending_drafts | List email drafts awaiting your approval. |
list_active_programs | List programs currently active or paused. |
list_draft_programs | List programs awaiting your approval, with enough detail to approve one directly. |
list_segments | List saved segments with current sizes. |
list_learnings | List entries from the Learning Ledger, Mara's Playbook. |
list_sent | List recent emails from the send queue, with status and failure reasons. |
contact_send_history | Everything one contact has been sent or has queued, across all programs. |
get_program_detail | Inspect one program's steps, copy, live variant arms, and its window (a deadline, if it has one) in depth. |
render_program_step | Render one program step's current subject, HTML, and plain text. |
get_journey_spine | Read your product journey state spine (funnel stages), in order, with each state's confirmed inbound events. |
get_domain_status | Read your sending domain's name, status, DNS records, and whether the send-from mailbox is provisioned. |
get_setup_status | Read the whole setup checklist in one call: what is connected, the spine, event mapping, programs, the sending domain, appearance, voice, and what to do next. |
list_feedback_themes | List Customer Voice themes: what real users have asked for, with requester counts, plan mix, and anonymized verbatims. |
Thirty tools act, each holding to the same approval promise above:
| Tool | What it does |
|---|---|
approve_draft | Approve one pending email draft so it can send. |
reject_draft | Reject one pending email draft so it will not send. |
draft_program | Draft a new event-entry program for you to approve, optionally time-boxed with an end date. |
draft_segment_program | Draft a new program that targets an existing saved segment, optionally time-boxed with an end date. |
draft_program_copy | Write a draft program's step copy so it can be previewed before approval. Safe to call twice; sends nothing and approves nothing. |
approve_program | Activate a draft program so it starts firing. |
edit_program | Edit an existing program's steps, timing, entry event, end date, whether its emails look plain or designed, or whether a step runs a rendering test. On a draft, an edit that changes what a step says retires the copy drafted under the old brief, so redraft it. |
pause_program | Pause one active program. |
resume_program | Resume one paused program back to active. |
archive_program | Archive one program for good. |
draft_segment | Create a new saved segment from a plain-English description. |
approve_segment | Activate a proposed segment so it can be targeted. |
add_learning | Add an entry to the Learning Ledger. |
retire_learning | Retire an entry from the Learning Ledger. |
add_suppression | Add an email address to the suppression list. |
rewrite_variant | Fix a program variant whose copy keeps failing to send. |
retire_variant | Retire one program variant; the bandit shifts send-share to the rest. Refused on one arm of a rendering test. |
dismiss_held_sends | Clear held sends (permanently failed, never retried) from the Activity feed. |
configure_journey_spine | Add, rename, reorder, or remove states on your product journey spine, set a state's kind, and set the goal. |
set_event_mapping | Confirm or reject what one event means: which state it advances a contact into, or that it is not a step. |
register_sending_domain | Register your sending domain and get back the DNS records to add. |
set_email_appearance | Set your signature logo, accent color, font, button style, theme tokens, and whether emails look plain or designed. |
capture_founder_voice | Hand Mara 1-3 real emails you wrote so she can distill and draft in your real voice. |
refire_contact_into_program | Re-enter one contact into a program by re-firing its entry event for them, to recover someone who genuinely missed it. |
set_product_context | Hand Mara your real feature list, plan limits, what activation means, and a catalog of real links her copy may use. |
set_sender_persona | Set who a program's emails speak as: you, your product, or let Mara pick per email type. |
set_sender_identity | Set who your emails are signed by: your name, and the role line under it. |
announce | Draft a one-off announcement, with a required reason, targeted by customer-journey state or segment. |
link_theme_issue | Link a Customer Voice theme to the issue you opened for it: any https URL, not only GitHub. |
mark_theme_shipped | Mark a Customer Voice theme shipped, optionally drafting the "you asked, we built it" announcement right away. |
Every tool that acts on one program takes programId. The same tools used to take three different names for that one id (programId, journeyId, journey), so the obvious chained call from list_draft_programs into get_program_detail failed. journeyId and journey are still accepted on all of them, and are marked deprecated in the schema, so an agent already holding the old spelling keeps working; pass programId in anything you save. Pass only one of the three: two names carrying different values is refused rather than resolved for you, since which program you meant would be a guess. The list and detail results carry programId too, with journeyId alongside it for one release, and draft_program_copy now returns programId the same way. list_active_programs and list_draft_programs return their rows under a programs array; the old journeys key still carries the identical array for one release, but it is deprecated too, so read programs. setup_events_ingestion's retargeted-welcome result carries the same convergence: read it under welcomeProgram, which itself carries programId; the deprecated welcomeJourney key (with journeyId) stays for one release.
configure_journey_spine shapes the state spine container only, the funnel stages themselves, not what any event means. Confirming or rejecting what an event means used to be a founder-only decision on the Programs page. It no longer is: set_event_mapping lets your agent make that call directly, on your explicit instruction, the same trust model as approve_program or pause_program. It appends to the same append-only mapping log the Programs page confirm UI writes to, so the latest decision always wins, and nothing is ever silently edited or removed.
register_sending_domain registers through the exact same path the dashboard domain page uses, so it never silently replaces a live domain: registering the same domain again just hands back the existing records, and a different domain already on file is refused with a note that changing it is a dashboard action. It also refuses, without registering anything, if the exact domain is already registered to a DIFFERENT Mara workspace, since domain identity isn't shared across workspaces; that refusal never names or otherwise identifies the other workspace.
set_email_appearance is a partial update: every field is optional and whatever you omit keeps its current saved value. A too-light accent color is accepted and saved rather than rejected, exactly like the Settings editor, since the actual guard is a render-time fallback to a neutral dark button fill, not a save-time rejection. It also accepts theme tokens: a web-safe font, the CTA button's style and corner radius, and a header logo, header background, page background, and text color. The font and button tokens apply to every email. The header, background, and text color tokens apply to designed emails only, and setting any of those three returns a warnings entry saying so plainly, so a bare success response is never mistaken for a change to your plain emails.
It also sets rendering, your default for how emails LOOK. plain is a founder-plain note on a white canvas: no header band, no panel, a plain text link instead of a button. designed adds a masthead band from your header logo, a card block, a filled button, and your theme font, page background, and text color. auto clears the default so each email follows its own purpose: milestones, product updates, and announcements come out designed, and everything else comes out plain. Designed emails get fewer replies than plain founder notes, and replies are what feed your Conversations and Customer Voice, so setting designed returns a warnings entry saying exactly that. Set it for one program instead of all of them with edit_program. A founder check-in always renders plain whatever you set, because that shape has no designed variant: a note with no call to action is the one email a masthead would contradict.
edit_program can also start a rendering test on one step. A rendering test is Mara writing ONE email and sending it two ways, as a plain note and as a designed one, then telling you which version your contacts reply to and which they click. The words are identical on both sides, so the only thing measured is how the email looks. Set renderingTest: true on a step edit to start one and false to stop. The step has to be running A/B testing already, or turn that on in the same edit with enableVariantTesting: true, since a rendering test is a variant set; ask for one on a step that writes a fresh email per recipient and it is refused rather than silently doing nothing. A step pinned to the founder check-in shape is refused too, because that shape has no designed version to test against. Both versions land as ONE approval, not two, since there is only one piece of copy. Results show up on Analytics with reply rate and click rate side by side, and once there is enough volume Mara proposes a winner in your queue. She never switches your program over on her own. Adopting a winner is two changes in one edit_program call: set rendering for the program, and set renderingTest back to false on the step that ran the test. Set the default without ending the test and nothing changes, because while a test is running each arm keeps sending as the version it was written for. retire_variant will not let you retire just one arm either, for the same reason: the two are one decision, so end the whole test instead of half of it. get_program_detail tells you which steps are running one and which rendering each arm carries.
capture_founder_voice takes raw writing samples only: 1-3 real emails, pasted as-is, plus an optional short note. There is no field for formality, cadence, or any other trait of your voice; Mara infers all of that herself from the samples. It returns only a plain-language summary of what she learned, never the underlying fingerprint. A missing model or a failed distillation returns a clean error and never touches any voice you already captured, and the two are told apart: no model configured means calling it again with the same samples will not help, only your own workspace setup can fix that, while a failed distillation run is worth a retry with the same arguments, since Mara already retries once herself before giving up.
refire_contact_into_program is a real action, not a diagnostic: on success it can result in a real email being drafted and sent to the contact you name, through the exact same pipeline a genuine signup uses. Use it only to recover someone who genuinely missed a program, for example an ingestion failure that swallowed their original signup event. It mints a fresh identity for every call, so calling it twice for the same contact fires twice rather than silently doing nothing the second time; only call it once per contact you mean to recover. It refuses plainly, and never claims a send happened, when the contact is already partway through the program, already received that step, suppressed, or the program has no matching entry trigger.
set_product_context is how you close the gap between what Mara scraped off your marketing site and what your product actually does. Every field is optional and this is a merge by default across top-level fields: only what you pass changes, everything you already told her stays. But each list you DO pass (features, plans, links) replaces the whole list saved for that field rather than adding to it, so include every item you want kept, not only the new one. Pass replace: true to reset the entire record instead. Once you set a catalog of real links, that catalog becomes the only source Mara's copy draws a link from, alongside your marketing site itself. Any URL outside your brand site must resolve to a host you explicitly list in allowedHosts on the same call, for example a separate app or docs subdomain; anything else, or a non-https URL, fails the whole call rather than saving partway.
announce is for one email because something actually happened. The reason is required and is one of shipped, changed, offer or ask, which is the anti-newsletter line in code rather than in a policy: there is no call shape that sends mail without naming which of the four things occurred, and nothing in this tool schedules a cadence. Target it by customer-journey state (ids from get_journey_spine) or by segment; targeting everyone is allowed only with the reason changed, since a pricing or terms change is the one thing that genuinely concerns every contact. Mara writes one variant per targeted state, so a trial user hears why it is worth upgrading for and a paying one hears how to use it. Anyone who has churned always gets their own win-back variant, whatever else you targeted, because the stage a contact reached furthest does not stop being their record once they leave, and telling someone who cancelled how to get more out of the product would be wrong. Mara reads churn from two signals, so it works whether or not you have a billing connector: a cancellation from Stripe, Polar, Lemon Squeezy or Paddle, or one of your own events that you mapped onto an exit state on your customer journey. Whichever happened most recently wins, so someone who came back after cancelling is treated as a customer again. If you attach an offer, each recipient gets their own redemption link on the button; the offer has to be active. A shipped or changed announcement speaks as your product by default, since a release and a pricing change are the product talking rather than you; an offer or an ask speaks as you. A sender persona you set for the whole workspace overrides that either way. Every draft lands awaiting your approval and nothing sends until you approve it. Contacts who are suppressed, out of consent, or already at their frequency cap are reported back to you rather than quietly emailed: a capped contact comes back as deferred, meaning reachable but not today. The frequency cap counts only email that actually went out, or that you approved and is queued to go: a draft still waiting on your approval is not something anyone received, so drafting two announcements in one sitting never defers people over the first one. If Mara cannot draft one of the variants, the result says so at the top: it comes back partial, names the group nobody was written for and how many people that is, and tells you to run it again for just that group. It is never reported as a clean success that counts only the variants that worked. Re-running the same announcement is safe either way: anyone who already has that exact announcement waiting for your approval is left out of the second run rather than given a duplicate of it. Spacing is checked again when you approve, not only when Mara drafts. If you approve an announcement for someone who already has an email going out today, it is not queued alongside it: the approval is kept, with any edits you made, and Mara sends that one later instead of stacking two on the same person in a day. Once you approve a non-offer announcement, a deferred contact is retried once with the same approved copy, no fresh draft and no second approval, within 7 days of your approval; past that a still unreached contact is marked expired and reported, never sent late. An offer-carrying announcement is the one exception: each recipient's redemption link is minted for them alone, so a deferred contact is never retried automatically there, reach them by running the announcement again.
set_sender_persona is the other half of the same pair: set_email_appearance decides how an email LOOKS, this decides what it SAYS, and the two resolve independently. It sets whether a program's emails speak as you (a named person writing one personal note) or as your product (the company speaking, never a named person). There is a third answer, auto: let Mara pick per email type. It is a real answer rather than a skip, so it clears the default (or the one program's override) and setup stops asking. Leave out a program and it sets your default for every future draft; name a program (by id or by a piece of its name) and it overrides that default for that one program only. Left alone, drafts speak as you for everything except milestone and product-update emails, which speak as your product by default. This never changes copy already drafted or a program already awaiting your approval, only what gets drafted next. Ask get_program_detail first if you want to see what a program currently resolves to before changing it.
set_sender_identity is the other question about the same email: not whether it speaks as a person, but WHICH person. It writes your name, and optionally the role line under it, into the same "Signed by" setting as Settings, Brand profile. Until it is set, Mara has nobody real to sign as, so she signs with your company or product name and refuses to invent a person: an email signed by somebody who does not exist is the one thing a founder cannot take back once it has gone out over their own domain. Your agent should ask you for your real name and pass what you answer, never guess one. It takes effect on the next draft; copy already drafted keeps the signature it was written with.
render_program_step is a pure preview, not a send simulation: it never sends and never saves anything, no matter what you pass it. Give it a program id and a step index and it renders that step's current content: its last real sent email if one exists, and otherwise its first drafted or approved variant arm, so a program that has been drafted but never sent can still be previewed. Pass a variantId (from get_program_detail's variant arms) to render one specific variant instead. It answers no_copy_yet only when the step truly has no copy anywhere yet, in which case call draft_program_copy first and try again. Two gaps keep a preview from being byte-identical to a real send, and the result always says so in its warnings: merge tags such as {{firstName}} are shown literally, since there is no recipient to resolve them against, and the unsubscribe footer a real send attaches is left off. The result is capped at 200KB of HTML; past that it comes back with truncated: true rather than a cut-off page. It also accepts two what-if fields for this one render, never saved. persona ("founder" or "brand") overrides which sender identity the render falls back to for a signature the drafted copy left blank, though it can never rewrite the copy's own voice or signature text, both already fixed at draft time. rendering ("plain" or "designed") previews the other skin: plain is a founder-plain note on a white canvas, designed adds a masthead band, a card block, a filled button, and your theme font and canvas. Leave rendering out and you get the step's real resolved rendering, which is what a live send would use. A founder check-in always renders plain whichever you pass, because that shape has no designed variant, and the result says so in its warnings rather than pretending the override took.
Customer Voice is what real users actually asked for, not what your agent inferred: list_feedback_themes reads the themes Mara has clustered from real feedback, each with its type, status, requester and item counts, a plan-mix breakdown when billing is connected, when it was last raised, up to three verbatim excerpts (capped around 200 characters) with the requester reduced to a first name only, and whether an issue is already linked. link_theme_issue attaches any https URL to a theme, a GitHub issue, a Linear ticket, a Jira ticket, or a plain doc (never one carrying an embedded username or password), so a later ship check can find the real tracker; it does not change the theme's status. mark_theme_shipped is the only way to flip a theme from active to shipped, a one-way action: an already-shipped or dismissed theme, or one not in your workspace, comes back a plain not-found rather than a silent no-op. Pass announce: true to also QUEUE the "you asked, we built it" email to that theme's requesters: marking a theme shipped is itself what arms your workspace's regular scheduled tick, so Mara drafts it automatically on her next pass, not synchronously inside this call, and it lands awaiting your approval; nothing sends until you approve it. Left false, marking a theme shipped just marks it and queues nothing.
One tool is Mara herself:
| Tool | What it does |
|---|---|
ask_mara | Hand Mara a question or an instruction and get her real answer back, run through the same model, system prompt, and tool loop as your dashboard chat companion. The turn is saved into your real chat history, so it shows up in your dashboard too. |
Two tools set up and verify your own product's event integration. They are MCP-only: they never appear in your dashboard chat, since a raw ingestion key has no business surfacing there.
| Tool | What it does |
|---|---|
setup_events_ingestion | Mint your Mara API ingestion key (or report its current status), hand back the exact endpoint URL, and map your signup event, retargeting the welcome program onto it. |
check_recent_events | Confirm recent events actually arrived, with event type, timing, contact identity, whether each row was a historical backfill or live traffic, and a per-connector health summary. |
If you connected before August 2026
The email-sequence tools used to be named *_journey: draft_journey, approve_journey, edit_journey, pause_journey, archive_journey, resume_journey, list_active_journeys, list_draft_journeys, get_journey_detail, draft_segment_journey, refire_contact_into_journey. We renamed them to *_program, because a program is the email sequence Mara runs and a journey is now only ever your customer's path through your product. The full mapping is in the glossary.
Every old name still works, so an agent mid-session does not break. They are marked deprecated in the tool list and will be removed in a future release, so prefer the *_program names in anything you save. The same applies to the program ARGUMENT: programId is the name now, and the older journeyId and journey spellings are accepted but deprecated. get_journey_spine and configure_journey_spine were not renamed: they operate on the customer journey, which is still called a journey.
Rate limits
The endpoint allows 60 calls per minute per key. ask_mara is stricter on top of that: 10 calls per minute per key, and 200 per day per workspace, since each call is a real model turn rather than a cheap read. Over a limit you get a clean tool error naming the wait, not a broken connection.
Verify it landed
Ask your agent to call list_active_programs or list_pending_drafts first. A working connection returns your real workspace data immediately; a wrong or revoked key returns an authorization error before anything else runs.
For AI coding agents
If you are an agent setting Mara up for a founder, run the mara-setup prompt: it is this checklist, served by the server, so it is always current. What follows is the same thing written out, for a client that cannot list prompts.
Connect first:
- Ask the founder to generate a key from Settings, Integrations, MCP (AI tools), and copy it into your config or the connect command. Never ask for it in plain chat if a copy button is available.
- Register the server with the client the founder is using (Claude Code, Claude Desktop, Cursor) using the blocks above.
- Confirm the connection with a read-only call.
get_setup_statusis the best first check: it also tells you what is already done.
Then the setup itself, in order:
get_setup_status. Read what is connected, the customer journey states, the mapping, the drafts, the domain, appearance, voice, and thenextActionslist. Skip whatever is already done.- Propose the customer journey. Work the states out of the code, name the one goal state, confirm both with the founder in your own chat, then apply with
configure_journey_spine. - Choose the event name for a new signup, then call
setup_events_ingestionwith it assignupEventType: it retargets the seeded welcome program onto that event, so it welcomes signups instead of the defaultsubscription-started. Then instrument that event (unless an auth connector already covers signup) and the transitions between those states: server side, off the request hot path, with a deterministic messageId per event. Three to five moments that matter, following the spec at /docs/api. Ifget_setup_statusreportswelcomeTriggerMismatch, follow its matchingnextActionsentry: a draft welcome is fixed by callingsetup_events_ingestionagain with the correctsignupEventType, but an already approved one (active or paused) is not, so it needsedit_programwith that program'sprogramIdandentryEventTypeinstead.setup_events_ingestiononly ever retargets a draft, and it refuses to create a second welcome program rather than leaving you with two. - Backfill the existing users without emailing them: identify everyone with current traits, then
trackonly the past transitions a program or segment needs, withhistorical: trueand the real past timestamp. Never send a live signup event for an existing user. set_event_mappingfor every event type you instrumented: which state it advances a contact into, or that it is not a step.set_product_contextwith the real features, plans and limits, what activation means, and a catalog of real in-app links. Every link must be absolute https on the founder's brand site host or on a host named inallowedHostson the same call, so pass the app or docs host when they differ from the marketing site: one unlisted host rejects the whole call.capture_founder_voice, but only with one to three things the founder actually wrote (a transactional email, a changelog entry), and only on their yes. Pass the rendered prose a reader would see, never the template file, its markup or its variables. Otherwise skip it.- Ask the founder, in your own chat, whether their emails should read as written by them personally or by the product itself. Only on an explicit choice, call
set_sender_personawith that choice. If they would rather Mara pick per email type, which is the default, leave it alone: do not call the tool without a real answer. - Ask the founder, in your own chat, for the name they want at the bottom of an email and the role line under it, then call
set_sender_identitywith what they answer. If the voice samples from step 7 all end with the same sign-off, offer that as the default rather than making them type it again. Never guess a name: until a real one is on file, Mara signs with the company or product name instead of inventing a person. set_email_appearancefrom the codebase's design tokens, if there are any. A guessed brand color is worse than the plain default.list_draft_programsfor the welcome program's id. A drafted program starts as a plan with no copy in it yet, so calldraft_program_copyon that id first: it writes each step's email the same way a live send would, lands it pending approval, and sends nothing. Safe to call twice; a step that already has copy comes back untouched. A step marked per-recipient writes a different email per recipient at send time, so describe that step rather than previewing it. Then callrender_program_stepon the first step, and show the founder the rendered subject and text. Pass along both of its warnings exactly as reported: merge tags such as{{firstName}}render literally, since there is no real recipient, and the unsubscribe footer a real send attaches is not included.- Hand the founder the subscription link and wait. Approving a program needs a live subscription:
approve_programrefuses outright without one, so this comes before the approval, not after it. Callget_setup_statusand readfounderOnlyBlocker: when it is set it carries the message to relay and the link to give them. Starting the trial or subscribing is theirs alone and no tool here can do it. When it is null, this is already done. get_program_detailon that same program for the actual subject lines and copy (the list call returns id, name, entry trigger and step count only). Show the founder the drafted welcome program and callapprove_programonly after they say yes.edit_programif they want changes: an edit that changes what a step says retires the copy written under the old brief and tells you how much it retired, so calldraft_program_copyagain before showing them anything, or you would be showing them the words they just asked you to change.get_domain_status, thenregister_sending_domainfor the subdomain they name. Add the DNS records yourself if you hold their provider's credentials, otherwise hand them over. Verification is asynchronous, so re-check rather than reporting it done.- Fire one real event and confirm it with
check_recent_events. A tool call returning is not evidence the integration works. get_setup_statusonce more, and report what is done and what is still verifying. Anything still innextActionsis what is left; an entry with no tool name on it is the founder's own step.
The rules that hold throughout:
- Never paste source code, file contents or a diff into any tool here. No tool has an input for them, and that is the promise Mara makes about your code.
- Never pass
rotate: truetosetup_events_ingestionon a routine check. That revokes the founder's live key. - Never approve anything the founder has not explicitly agreed to. Every action tool still lands
pending_approvalin the dashboard, or waits on the founder's own autonomy setting, so do not tell them something "sent" because a call returned; checklist_sentor the dashboard. - Confirm the goal state before applying the customer journey states. It decides what Mara optimizes for.
- Treat every key like any other credential: one per machine, revoked the moment a machine is retired.