Sender is an email marketing platform built to help you create, automate, and send emails that people want to read – without complicated setup or high costs.
Cursor is an AI-first code editor. It loads MCP servers from its settings and lets its agent call their tools as part of a task.
This guide connects Sender MCP to Cursor through MewCP, so Cursor can use Sender MCP's 43 tools, like get_account_details, list_email_campaigns and get_email_campaign, without your Sender MCP credentials ever reaching the app.
Sender MCP signs in through managed OAuth. MewCP stores the token encrypted, refreshes it before it expires and injects it only at call time. Your agent never holds it, so it can never leak it.
More apps
Manage Gmail end to end with tools for sending, reading, replying to and organizing emails. Work with threads, labels, drafts, attachments and inbox workflows while keeping communication and email automation in one place.
Manage your Google Business Profile, business information and customer engagement. Update listings, publish content, monitor reviews and maintain your local presence across Google services.
Microsoft Outlook MCP Server gives AI agents full control over Outlook email and calendar workflows, including reading and sending emails, managing attachments, organizing mail, scheduling and updating meetings, managing contacts, and creating tasks.
Connect Zoho Mail to Claude, ChatGPT and any AI agent. Search, read, send and reply to emails, manage drafts, folders and labels, all through one secure connection.
Other clients
Sender MCP works the same way in every client below. Pick yours for its own guide.
Free to start.
Scope access to the exact Sender MCP tools and accounts you allow, work and personal side by side. Anything outside that scope is refused at call time, even when the model asks for it.
Each call is logged: what ran, on which account, and whether it worked. Failures come back structured, so the agent knows whether to retry instead of guessing.
Every app sits behind one secure MCP URL, and there is no server for you to deploy or keep running. Add an integration in the dashboard and it is live on the next call, with no config to change and no new credentials to wire up. Tools load only when a task needs them, so your agent stays fast and focused however many apps you connect.
A browser window opens the first time the client connects. Sign in and approve access. No API key needed.
Start a new chat and ask: "List the tools you can use from MewCP." You should see your connected apps.
Get the currently authenticated Sender account: id, name, timezone, country, status, active plan, and the calling user. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
List email campaigns in the authenticated Sender account. Supports filtering by status and a subject search. Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch a single email campaign by its hashed id. Returns campaign metadata (subject, sender, status, schedule, recipient count, follow-up settings), its groups and excluded groups, its segments and excluded segments, engagement reports (sent/opens/clicks/bounces plus spam/hard-bounce/unsubscribe percentages — null until the campaign has been sent), and the body under `html`: `content`, its `content_type` ("html", "text" — basic personal-style HTML, or "drag_and_drop" — designed in the dashboard builder), and `editable_via_mcp` (whether update_email_campaign_content can replace it). All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch the performance report for a single sent email campaign by its hashed id. Returns total_sent, raw counts (opens, clicks, unsubscribes, spam_reports, soft_bounces, hard_bounces, bounces, unopened), percentage rates against the appropriate denominator (delivered vs. total sent), and a top_links breakdown of clicked URLs with click counts. When the account is entitled to revenue reporting, also returns purchase_count. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Create a DRAFT email campaign in the authenticated Sender account. Set the subject, from_name, from_email (must be an address on a VERIFIED sending domain — use list_domains and pick one with ready_to_send true), an optional preheader, and an optional body via content_type (html or text) + content; the body can also be set or replaced later with update_email_campaign_content. Personalization must use Liquid syntax ({{ firstname }}, {{ unsubscribe_link }}) — legacy {$variable} tokens are stored as-is and would reach subscribers as literal text (the response warns if any are found). Target the campaign by passing group ids (groups) and/or segment ids (segments) from the list tools, or send_to_all. Optional auto follow-up and Google Analytics settings are supported. The campaign is created as a DRAFT and is not sent. Returns the created campaign in the same shape as get_email_campaign; all identifiers are strings and timestamps are ISO 8601 UTC. Pass a stable idempotency_key to make retries safe.
Set the body of a DRAFT email campaign. content_type "html" takes a full raw HTML email you design yourself. content_type "text" is NOT literal plain text — it is a simple person-to-person style email using basic HTML markup (<p>, <br>, <a>), the way an email written in Gmail looks; submit only that inner markup and a full document wrapper is added automatically at send time. Personalization must use Liquid syntax ({{ firstname }}, {{ unsubscribe_link }}) — legacy {$variable} tokens are stored as-is and would reach subscribers as literal text (the response warns if any are found). Wholesale replacement: the previous body is discarded. Campaigns designed with the dashboard's drag & drop editor are protected: replacing one requires overwrite: true and permanently discards the design; creating drag & drop content is not possible here. Only draft campaigns can be edited. Returns the updated campaign in the same shape as get_email_campaign, plus warnings. All identifiers are hashed strings.
List automations (workflows) in the authenticated Sender account. Supports filtering by status (active, draft, paused) and a title search. Each item includes an engagement report (emails_sent, opens, clicks). Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch a single automation (workflow) by its id. Returns the workflow metadata (title, status, description), a per-email engagement report, and the full automation graph: `trigger` (type, settings, re-entry `frequency`, and any entry-time event/audience filters) plus `steps`, a nested tree where linear steps run in array order, condition steps branch via `then`/`else` arrays, and split steps via `a`/`b`. `steps_for_humans` is an English outline of the same graph — read it to verify branch direction. `incomplete_steps` lists steps that still need configuration with the reason; a workflow cannot be activated while any remain. Message bodies are not embedded here — fetch them with get_workflow_step_content and set email bodies with update_workflow_email_content (raw HTML or plain text). Steps or conditions built in the dashboard that the public vocabulary does not cover appear with `supported: false` and their raw definition instead of being omitted. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch the performance report for a single automation (workflow) by its id. Returns completed_count, subscribers_in_workflow, total_emails_sent, and average engagement rates (average_open_rate, average_click_rate, average_unsub_rate, average_bounce_rate, average_complaints_rate). When the account is entitled to revenue reporting, also returns a revenue block (revenue, number_of_orders, roi, revenue_per_customer, currency). All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Create an automation (workflow) as a draft: the whole graph in one call — a trigger plus a nested steps tree. Linear steps run in array order; a condition step branches via `then`/`else` arrays; a split step branches via `a`/`b`. Steps after a condition/split in the same array are invalid — continue inside the branch arrays. Give a step a `key` to reference it from a workflow_email_activity / workflow_sms_activity condition (`if.step`). Email steps carry only the envelope (subject, from_name, from_email, preheader); set bodies afterwards with update_workflow_email_content (raw HTML or plain text) or the dashboard editor — the response's `incomplete_steps` lists exactly what remains before the workflow can be activated (activation is a separate, deliberate action). Use validate_workflow first to check a document without creating anything, and read the response's `steps_for_humans` to verify branch direction matches your intent. Pass a stable idempotency_key to make retries safe. Creation is limited to 50 workflows per account per day. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Dry-run validation — creates and changes nothing. Two modes: pass `trigger` + `steps` (the same document create_workflow accepts) to check it before creating — structural problems and unknown referenced ids are reported with the path into the document; or pass `workflow_id` to run the activation checklist on an existing workflow — every problem that would block activation (unfinished steps, missing email content, unverified sending domain, account limits) reported at once. Returns {valid, problems, warnings}. Document mode cannot check email content or sending domains (emails do not exist yet) — those surface on the existing workflow after creation. All identifiers are hashed strings.
Rename a workflow. Everything else is edited elsewhere: the trigger and steps with update_workflow_step / add_workflow_steps / delete_workflow_step, message bodies with the content tools. The workflow must not be active. Returns the updated workflow in the same shape as get_workflow. All identifiers are hashed strings.
Insert steps into an existing workflow — after any step, including mid-graph. `steps` is a list in the same nested shape create_workflow accepts (conditions branch via then/else, splits via a/b). The insertion point is (parent_step_id, branch): branch is "next" after a linear step or the trigger, "then"/"else" after a condition, "a"/"b" after a split. If that slot already has steps, they are re-attached after the inserted fragment — when the fragment ends with a condition or split, pass continue_branch to say which branch they continue on. Conditions in the fragment may reference existing email/sms steps via step_id, or steps in the fragment via key + step. The workflow must not be active. Returns the updated graph plus created_step_ids — read steps_for_humans to verify placement. All identifiers are hashed strings.
Reconfigure one workflow step in place, addressed by its id from get_workflow. What you can set depends on the step's type (which is immutable): email — the envelope (subject, from_name, from_email, preheader; bodies via update_workflow_email_content); delay — the wait (amount+unit / until / relative_to_field_id, only_on); condition — a replacement `if` object (wholesale; reference existing email/sms steps via if.step_id); split — a_percent/b_percent; action — a replacement action + its settings; trigger (the graph's `trigger.id` step) — a replacement `trigger` object (wholesale: type, settings, frequency, entry filters; replacements that would break context conditions are refused); any step — title. Structure (branches, order) is edited with add_workflow_steps / delete_workflow_step. The workflow must not be active. Returns the updated graph — read steps_for_humans to verify. All identifiers are hashed strings.
Delete one workflow step. By default the step is spliced out — its children take its place. When the step branches (condition/split), pass keep_branch (then/else or a/b) to say which branch survives; the other branch is deleted with everything in it. Pass delete_children: true to delete the step together with everything below it instead. DESTRUCTIVE: deleting steps permanently drops any subscribers currently waiting at them (they do not finish the workflow) — the tool refuses in that case unless force: true acknowledges it. Deleted email/SMS messages are discarded too. The trigger cannot be deleted (replace it with update_workflow), and the workflow must not be active. Returns the updated graph plus deleted_step_ids and dropped_subscribers. All identifiers are hashed strings.
Turn a workflow live. Runs the full pre-flight checklist first (the same one validate_workflow reports) — if anything blocks activation, NOTHING goes live and every problem is returned at once as {activated: false, problems}. On success the workflow starts processing: new subscribers enter via the trigger, and any subscribers who were mid-journey when it was last deactivated resume from where they stopped. As part of activation, unfinished step fragments not connected to the trigger are cleaned up (the same housekeeping the dashboard does). Activation is deliberate — nothing else in this toolset activates a workflow. Use validate_workflow first to check without side effects. All identifiers are hashed strings.
Stop an active workflow: its status returns to draft, no new subscribers enter, and no further steps run. Nothing is lost — subscribers mid-journey keep their place and resume when the workflow is re-activated (activate_workflow). Deactivating is required before any editing tool can change the workflow. All identifiers are hashed strings.
Fetch the message content of one workflow step. For an email step: the envelope (subject, from_name, from_email, preheader), the body (`content`), and its `content_type` — "html" (fully designed raw HTML), "text" (a simple personal-style email: basic HTML inner markup, auto-wrapped into a full document at send time — not literal plain text), "drag_and_drop" (designed in the dashboard builder), or null when no content exists yet. `editable_via_mcp` says whether update_workflow_email_content can write this step's body (drag & drop designs can only be edited in the dashboard). For an SMS step: the message text. Other step types have no content. All identifiers are hashed strings.
Set the body of a workflow email step. content_type "html" takes a full raw HTML email you design yourself. content_type "text" is NOT literal plain text — it is a simple person-to-person style email using basic HTML markup (<p>, <br>, <a>, <strong>), the way an email written in Gmail looks; submit only that inner markup (line breaks need tags), and a full HTML document wrapper (<html>, <body>, styling) is added automatically at send time, exactly like the dashboard's "plain" editor. Personalization must use standard Liquid syntax — {{ firstname }}, {{ email }}, {{ unsubscribe_link }} — the content is stored exactly as passed; legacy {$variable} tokens are NOT converted and would reach subscribers as literal text (the response warns if any are found). Wholesale replacement: the previous body of that step is discarded. The workflow must not be active (deactivate first — subscribers keep their place and resume on re-activation). Emails designed with the dashboard's drag & drop editor are protected: replacing one requires overwrite: true and permanently discards the design; creating drag & drop content is not possible here. The response returns the stored content (same shape as get_workflow_step_content) plus whether the step is now complete. All identifiers are hashed strings.
Set the message and sending phone number of a workflow SMS step. The content is stored exactly as passed — use Liquid syntax for personalization ({{ firstname }}); legacy {$variable} tokens are NOT converted and the response warns if any are found. An opt-out line and the organization name are appended automatically at send time (the response's content_as_sent shows the final message), and an SMS is billed per 160-character segment (70 with non-GSM characters), so keep it short. phone_number_id must be an active number from list_phone_numbers. The workflow must not be active (deactivate first). Returns the stored content plus whether the step is now complete. All identifiers are hashed strings.
List the account's SMS sending phone numbers: id, the number, type (TOLLFREE / SHORTCODE / 10DLC / ALPHANUMERIC), purpose (TRANSACTIONAL / BROADCAST), and whether it is active. Workflow SMS steps (update_workflow_sms_content) need an active number's id. All identifiers are hashed strings.
List the account's email sending domains with their authentication status: verified plus the SPF, DKIM and DMARC DNS checks, and ready_to_send — true when all four pass, meaning a from_email address on that domain can be used for campaigns and workflow email steps (and activation will not be blocked on domain grounds). Domains missing records are finished in the dashboard (DNS setup). All identifiers are hashed strings.
List subscriber groups in the authenticated Sender account. Supports a case-insensitive title search. Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch a single subscriber group by its id. Returns the group title and subscriber counts (recipients, active, unsubscribed, bounced, phone). All identifiers are hashed strings; timestamps are ISO 8601 UTC.
List segments (dynamic subscriber filters) in the authenticated Sender account. Supports a case-insensitive name search. Each item carries its conditions as `match` plus `condition_sets`, in the same vocabulary create_segment accepts, plus `conditions_for_humans` rendering them as readable English. An item with `counts_pending` true has not been counted yet, so its counts are all 0 rather than reflecting how many subscribers it matches. Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch a single segment (dynamic subscriber filter) by its id. Returns the segment name, subscriber counts (recipients, active, unsubscribed, bounced, phone), and its conditions as `match` plus `condition_sets` — the same vocabulary create_segment accepts, so a segment can be read, edited and written back. A condition built in the dashboard that has no equivalent in that vocabulary is returned with "supported": false and its raw stored form rather than omitted. `conditions_for_humans` renders the same conditions as readable English, one line per condition set — use it to confirm a segment means what was intended before acting on it. When `counts_pending` is true the counts have not been computed yet and are all 0 — that is not the same as the segment matching nobody; call again after a short wait. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Create a segment: a saved set of conditions that dynamically selects subscribers in the authenticated Sender account. Conditions are organised in two levels. `condition_sets` are joined by the top-level `match`; conditions inside a set are joined by that set's `match`. Most segments need a single set. Every condition has two independent axes. `operator` (with `value`, `range`, `values` and `unit`) selects WHICH records match. `times` (with `times_count`) says HOW MANY there must be. Negation lives on the second axis: "never opened anything" is an unfiltered set of opens with `times: "never"`, not an operator. Omitting `operator` on a type that allows it means "no restriction on which records match". Returns the created segment, including `conditions_for_humans` — the conditions rendered as readable English. Check it against what was asked for: "clicked any of 6 campaigns" and "clicked a specific campaign and clicked a specific campaign" are near-identical structures with very different audiences. Subscriber counts are computed by a background job rather than on write, so a newly created segment comes back with `counts_pending: true` and every count at 0. That zero means "not counted yet", not "matches nobody" — do not read it as a sign the conditions are wrong. Call get_segment again after a short wait; the counts are final once `counts_pending` is false. Segment names must be unique in the account. Pass a stable idempotency_key to make retries safe. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Rename a segment and/or replace its conditions. Provide `name`, `condition_sets`, or both; anything omitted is left unchanged. Conditions are replaced wholesale, not merged: a segment is one boolean expression, so a partial update would silently change who it matches. To edit a segment, read it with get_segment, adjust the `condition_sets` it returns, and send the whole structure back. Condition syntax is identical to create_segment: `condition_sets` joined by the top-level `match`, conditions inside a set joined by that set's `match`, with `operator` selecting WHICH records match and `times` saying HOW MANY. A segment containing conditions that get_segment reported as "supported": false cannot be updated through this tool without dropping them — those must be edited in the dashboard. Returns the updated segment, including `conditions_for_humans` — the conditions rendered as readable English, worth checking against what was intended. Changing conditions invalidates the subscriber counts, which are then recomputed by a background job — so the response comes back with `counts_pending: true` and every count at 0. That zero means "not counted yet", not "matches nobody". Call get_segment again after a short wait; the counts are final once `counts_pending` is false. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
List subscribers in the authenticated Sender account. Optionally filter by channel status (status + channel), by group id, or by an email substring search. Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Fetch a single subscriber in the authenticated Sender account by id, email address, or phone number. Returns the subscriber with channel statuses, groups, custom field values and location. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Create a new subscriber in the authenticated Sender account. Provide an email and/or a phone number (at least one is required), optional firstname/lastname, groups (group ids) and custom fields (a name => value object). Returns the created subscriber. Pass a stable idempotency_key to make retries safe. All identifiers are hashed strings; timestamps are ISO 8601 UTC.
Update an existing subscriber in the authenticated Sender account, identified by id, email address, or phone number. Only the provided attributes are changed (firstname, lastname, email, phone, groups, custom fields). Returns the updated subscriber. All identifiers are encoded strings; timestamps are ISO 8601 UTC.
Subscribe (reactivate) one or more subscribers on the given channels in the authenticated Sender account. Each subscriber is identified by id, email address, or phone number. Channels must be specified (email, sms, temail, tsms, or all), where temail is the transactional email channel and tsms is the transactional SMS channel. Already-active subscribers are left unchanged. Returns a status block {processed, queued, message}.
Unsubscribe one or more subscribers from the given channels in the authenticated Sender account. Each subscriber is identified by id, email address, or phone number. Channels must be specified (email, sms, temail, tsms, or all), where temail is the transactional email channel and tsms is the transactional SMS channel. Already-unsubscribed subscribers are left unchanged. Returns a status block {processed, queued, message}.
List the image folders (categories) in the authenticated Sender account. Returns {items: [{id, title, editable}]}; the id is a string to pass as the "category" argument to upload_image_from_file or upload_image_from_url. Omit "category" on those tools to store an image uncategorized ("All images").
Upload an image into the authenticated Sender account's image library from base64-encoded file bytes. Provide file_base64 (the raw image bytes, base64-encoded) and filename (used to derive the title and extension). Allowed types: jpg, jpeg, png, gif, bmp, svg; maximum decoded size 10 MB. Optionally pass a category (image folder id) to file it under; omit for uncategorized. Returns the created image (id, public url, dimensions). Pass a stable idempotency_key to make retries safe.
Upload an image into the authenticated Sender account's image library by fetching it from a public https URL. The URL must be https, contain no credentials, and resolve to a public host (private, loopback, and link-local addresses are rejected). Optionally pass a category id (image folder id) to file it under; omit for uncategorized. Returns the created image (id, public url, dimensions). Pass a stable idempotency_key to make retries safe.
List the custom event types available to the authenticated Sender account (both account-specific and public/built-in types). Supports a name search. Returns a paginated envelope {items, page, per_page, total, has_more}. Each item has a id usable as event_type_id in get_events. Timestamps are ISO 8601 UTC.
List custom event instances for a single event type in the authenticated Sender account, newest first. Requires event_type_id (from get_event_types). Optionally filter by subscriber_id and a since/until time window to bound the query. Returns a paginated envelope {items, page, per_page, total, has_more}; each event has its subscriber_id (hashed), email, value, value_currency, properties map, and an ISO 8601 UTC created_at.
Create a custom event type in the authenticated Sender account so it can be referenced before any event of that type has been sent (for example in a workflow trigger or a segment). The name must be unique in the account and must not be the name of a built-in event type. If an event type with this name was deleted earlier it is restored (same id, so past events and workflow references reconnect) and its description is updated when one is provided. Returns the created event type (see get_event_types for the shape). Pass a stable idempotency_key to make retries safe. Identifiers are hashed strings; timestamps are ISO 8601 UTC.
List the custom fields of the authenticated Sender account, plus the shared system fields (First name, Last name; is_system true). Supports a title search and a type filter. Returns a paginated envelope {items, page, per_page, total, has_more}. Each item has id, title, name, merge_tag, type, options, default_value, position and is_system. Use a field name as the key in the fields object of create_subscriber and update_subscriber. Use the hashed id as field_id in create_segment, and in create_workflow and update_workflow_step for fields with is_system false only. Identifiers are hashed strings; timestamps are ISO 8601 UTC.
Create a custom field in the authenticated Sender account. Provide a title and a type (text, number, date, datetime or dropdown). A dropdown needs options (a list of {value, selected?}); default_value, when set for a dropdown, must be one of the option values. The field name used as the key in subscriber fields is derived from the title. Titles that produce a name already in use, or a reserved name such as email or phone, are refused. Returns the created field (see get_fields for the shape). Pass a stable idempotency_key to make retries safe. Identifiers are hashed strings; timestamps are ISO 8601 UTC.
List the ecommerce stores connected to the authenticated Sender account, with their platform and domain. Use this to resolve the store_id required by the purchased_product segment condition in create_segment. Supports a case-insensitive search over name and domain. Returns a paginated envelope {items, page, per_page, total, has_more}. All identifiers are hashed strings; timestamps are ISO 8601 UTC.