DevelopersConnect an AI assistant (MCP)

Connect an AI assistant (MCP)

Copy page

Connect Claude, Cursor, VS Code or Gemini CLI to Workflow Transactional Email over MCP to audit setups and debug sends.

The app runs an MCP server, so an AI assistant can inspect your setup, build and translate email layouts, manage your HTTP requests and tidy up your media library directly, instead of you copying configuration back and forth.

Useful things it can do: draft a marketing layout with your footer texts, translate a layout into every language you sell in, audit which email layouts reference a secret that is about to rotate, find every uploaded image nothing uses any more, explain why yesterday's sends failed, or build a new HTTP request from a third-party API's documentation.

Endpoint

text
POST https://shopify.workflow-transactional-email.app/api/mcp

Transport is Streamable HTTP. Authentication is the same bearer key as the REST API - see Authentication and API keys.

Connecting

The MCP tab on the app's Developer page generates a copy-ready command with your URL and key already filled in, for Claude, Claude Desktop, Cursor, VS Code and Gemini CLI. Use that rather than typing it by hand.

For Claude Code the command looks like this:

bash
claude mcp add --transport http flow-transactional-email \
  https://shopify.workflow-transactional-email.app/api/mcp \
  --header "Authorization: Bearer fak_your_key_here"

For editors that use a JSON config, the shape is:

~/.cursor/mcp.jsonjson
{
  "mcpServers": {
    "flow-transactional-email": {
      "url": "https://shopify.workflow-transactional-email.app/api/mcp",
      "headers": {
        "Authorization": "Bearer fak_your_key_here"
      }
    }
  }
}

Available tools

The tool set reflects your key's level. A read-only key does not merely get refused when it calls a write tool - it never sees that tool in the list at all, so an assistant cannot be talked into attempting it.

Read (15 tools)

Email layouts: list_email_templates, get_email_template, get_layout_schema, list_template_translations, list_template_versions, get_template_version. list_email_templates takes q, category (transactional or marketing), page and limit.

Email senders: list_smtp_configs, list_senders

Secrets: list_secret_keys (names only)

HTTP requests: list_http_requests, get_http_request

Media: list_files (pass usage: true to see what still references each file)

History: list_history (filter by status, including SKIPPED for marketing emails whose recipients had all unsubscribed), get_history_entry, get_stats

Read & write (adds 12)

Email layouts: create_email_template, update_email_template, delete_email_template

Languages: set_template_translation, delete_template_translation, promote_template_translation

Versions: restore_template_version, restore_template_github_version

HTTP requests: create_http_request, update_http_request, delete_http_request

Media: delete_file

Read, write & execute (adds 1)

test_http_request - runs a configured HTTP request against the real target.

Building layouts with an assistant

Ask the assistant to read get_layout_schema first: it describes the visual design format with every block's defaults, the Liquid you can use and the limits, generated from the builder itself. Then create_email_template takes a name, a subject, and a bodyDesign (visual, the default) or an HTML body (text). The layout appears in the app right away and every change is kept in its version history, so nothing an assistant does is beyond undo.

Two fields decide how a layout behaves as an email:

  • category: transactional (default) or marketing. A marketing layout skips unsubscribed recipients and gets an unsubscribe link and footer; see Marketing emails and unsubscribes.
  • marketingTexts: the footer texts of a marketing layout instead of the shop's from Settings, { unsubscribeText, unsubscribeLinkLabel, companyDetails }, every field optional; null means the Settings texts. The unsubscribe sentence must contain {{ unsubscribe_link }} or {{ unsubscribe_url }}.

set_template_translation creates or replaces one language with the full translated content. For a text layout it can also carry attachments (a list = this language's own files, null = the layout's, omitted = unchanged), and for a marketing layout marketingTexts for that language on top of the layout's. promote_template_translation makes a language the layout's main language; the two trade places, so nothing is lost.

A good first request:

"Read the layout schema, then translate my layout 'Order shipped' into German and French. Keep every Liquid tag and URL exactly as it is."

Every write is checked like a save in the app: Liquid must parse, files must be your own, and the tool answers with the field that failed.

Tidying the media library

A good use for an assistant. Ask it to list your files with usage included, then delete the ones nothing references:

"List my uploaded files with usage and tell me which ones nothing is using. Then delete those."

delete_file is refused with a 409 if any email layout or header/footer preset still references the file, and the refusal names what is using it - so the assistant cannot remove a logo a live email still needs, even if you ask it to.

What the assistant can never see

There is deliberately no tool that returns a secret value, an SMTP username or password, or a mailbox sign-in token. list_secret_keys returns names only.

That is the point of the design: you can ask an assistant to write an HTTP request that authenticates with your payment provider, and it will reference {{ secrets.stripeApiKey }} correctly without the key itself ever entering the model's context.

History returned through MCP is masked exactly as it is over REST. The same caveat applies - masking works on field names and value patterns, and free-text fields can still carry personal data into the conversation. Think about that before pointing an assistant at a large history range.