Tools
Every tool Peleka's MCP server exposes, grouped by resource, with what each one does and requires.
56 tools, split across six resource groups below. Each one is marked Read or Write: a read-scoped connection can call every Read tool and none of the Write ones. A handful of Write tools are also marked Confirm-gated: see Confirmation before anything irreversible for what that means in practice.
List tools share one response shape, so it's documented once here instead of on all eight of them: { items, total, offset, hasMore }. limit and offset are both input parameters (default limit varies by tool, offset always defaults to 0); pass the previous call's offset + items.length as the next offset to page forward, and stop once hasMore is false.
Contacts
peleka_list_contacts
Read. Lists contacts in the workspace, newest first. Filter by status (subscribed, unsubscribed, bounced, complained, cleaned, pending), tagId, or segmentId; the last two are mutually compatible with status but not really meant to combine with each other in one call. limit defaults to 50, caps at 100.
peleka_get_contact
Read. Gets one contact by contactId or by email. Provide exactly one; returns null (not an error) if nothing matches.
peleka_list_custom_fields
Read. No parameters. Lists the workspace's custom field definitions: key, label, and fieldType (text, number, boolean, date, url, or month_day). Check here before setting customFields on peleka_upsert_contact, since an unrecognized key isn't rejected: it's just stored as an unregistered field. month_day is the one type actually validated, and it must be "MM/DD".
peleka_upsert_contact
Write, idempotent. Creates a contact if the email doesn't exist yet, or updates the existing one if it does. Can attach tagIds, add to segmentIds, and set customFields in the same call, customFields keys should come from peleka_list_custom_fields. A soft-deleted contact with that email is restored rather than left alone.
peleka_add_tag
Write, idempotent. Adds one tag (tagId) to one contact (contactId). Adding a tag the contact already has is a no-op, not an error.
peleka_remove_tag
Write, destructive. Removes one tag from one contact. If that tag drives an active automation's trigger, the response includes a warning: contacts already enrolled aren't affected, but this contact won't be re-triggered by the tag going forward.
peleka_bulk_tag_contacts
Write, destructive. Adds or removes (action) one tag across many contacts at once, either every contact in a segmentId or an explicit contactIds list. Provide exactly one of the two targeting params.
peleka_get_contact_events
Read. Returns activity counts by type for one contact (contactId) plus the most recent activity timestamp. This is a summary, not a log: it won't tell you which form was submitted or why an email bounced, just that a form_submitted or bounced event happened and how many times.
peleka_get_contact_engagement
Read. Email engagement stats for one contact (contactId): counts by event type (opened, clicked, and so on).
peleka_create_custom_field
Write. Defines a new custom field: key (normalized to a cf_ prefix automatically), label, fieldType (text, number, boolean, date, url, or month_day), optional subscriberEditable. Once created, it can be set via peleka_upsert_contact, referenced in a custom condition in peleka_build_automation or a segment's rules, and shows up in peleka_list_custom_fields. Rejected if a field with that key already exists in the workspace.
peleka_update_custom_field
Write. Renames a custom field or changes subscriberEditable, referenced by key (prefix optional). The field's fieldType can't be changed after creation; create a new one instead if the type is wrong.
peleka_list_suppressed_emails
Read. No parameters. Lists every address on the workspace's suppression list, each with a reason: manual, bounce, complaint, or unsubscribe. This is a permanent send-block distinct from a contact's own status field: it survives re-import or a future resubscribe through a form.
peleka_add_suppressed_email
Write. Permanently blocks one email from ever receiving mail from this workspace again. Use for an explicit compliance-driven request ("make sure we never email this address again"), not as a substitute for a normal unsubscribe. reason is always recorded as manual; bounce/complaint are only ever set automatically from real delivery events. Adding an address already on the list is a no-op.
peleka_remove_suppressed_email
Write. Removes one email from the suppression list. Entries with reason: 'complaint' can't be removed this way: a spam complaint is a direct signal from the recipient's mailbox provider, and undoing that protection is Peleka staff-only, to protect shared sender reputation.
Segments & tags
peleka_list_segments
Read. Lists segments in the workspace. No filters, just limit/offset.
peleka_list_tags
Read. Lists tags in the workspace. Same shape as peleka_list_segments.
peleka_get_segment_contacts
Read. Lists the contacts inside one segment (segmentId), paginated the same way as peleka_list_contacts.
peleka_get_segment_summary
Read. A segment's name, type, contact count, filter criteria, and aggregate engagement (segmentId), without listing individual contacts. Use this instead of peleka_get_segment_contacts when the question is "how is this segment doing," not "who's in it."
peleka_estimate_segment_size
Read. Previews how many contacts a set of dynamic-segment rules would currently match, without creating anything. Worth calling before peleka_create_segment/peleka_update_segment whenever the rules are non-trivial: a wrong field/op/value combination that's merely plausible (not structurally invalid) won't get rejected, it'll just quietly match the wrong contacts, and an unexpected count here is the cheapest way to catch that early.
peleka_create_segment
Write. Creates a segment: name, optional description/icon/color, type ('static' by default, or 'dynamic'). A 'static' segment starts empty; this tool doesn't seed members. A 'dynamic' segment needs rules: an operator ('AND'/'OR') plus a list of conditions. type: 'dynamic' and rules are required together in both directions: one without the other is rejected, since rules given to a static segment would just sit there unevaluated. Each condition matches a contact attribute (status, email, firstName, lastName, phone, source), a date field (subscribedAt, createdAt, bouncedAt, complainedAt), tag membership, a custom field (field: 'custom' + customKey), or a purchase-event fact (event.order_placed, event.abandoned_cart, event.order_count, event.total_spent, event.days_since_last_order). Valid op values depend on which field is used, and an invalid combination is rejected outright rather than silently dropped from the query. Rejected if a segment with this name already exists.
peleka_update_segment
Write. Edits an existing segment (segmentId): name, description, color, and/or rules, each independently optional. rules replaces the entire condition set, it does not merge with what's there; call peleka_get_segment_summary first to see the current rules if only part of them need to change. rules is rejected on a segment that isn't already 'dynamic', since it would just sit there unevaluated. type and icon can't be changed after creation.
peleka_create_tag
Write. Creates a tag: name, optional hex color. Rejected if a tag with this name already exists in the workspace.
peleka_update_tag
Write. Renames a tag or changes its color (tagId). Rejected if the new name collides with another tag in the workspace.
Broadcasts
peleka_list_broadcasts
Read. Lists broadcasts in the workspace, newest first. Filter by status. limit defaults to 20, caps at 100.
peleka_get_broadcast_stats
Read. Event counts (sent, opened, clicked, bounced, and so on) for one broadcast (broadcastId).
peleka_list_senders
Read. No parameters. Lists the workspace's sender addresses with their verification status (verified, pending, and so on) and whether each is the default. Every broadcast-creation tool below requires fromEmail to be a verified sender for the workspace; check here first rather than guessing an address.
peleka_create_broadcast
Write. Creates a draft broadcast: name, subject, fromName/fromEmail (must be a verified sender, see peleka_list_senders), and plain-text/simple-HTML bodyText. The audience is not optional to think about: pass targetSegmentId to restrict it to one segment, targetTagId for one tag, or confirmedAllContacts: true for every subscribed contact in the workspace. Omitting all three is rejected outright rather than quietly defaulting to everyone: call peleka_list_segments/peleka_list_tags and ask the user which they want first. targetSegmentId and targetTagId can't be combined in one call; a group that needs both a tag and other criteria has to be an existing (or newly built) segment that already includes the tag as one of its conditions. For a call-to-action, pass buttonText/buttonUrl rather than writing a styled link into bodyText: that gets a real, properly-styled button block instead of markup that only survives in a raw preview and degrades the moment the broadcast is opened in the dashboard, and it defaults to the workspace's own brand color (see peleka_get_workspace_context) rather than a fixed color, so it doesn't look off-brand. Same idea for highlightText (optionally with highlightBackgroundColor), for something like a discount code that should stand out in its own box: bodyText must contain a {{highlight}} marker at the exact point the box should appear, even if that's the very end; the call is rejected otherwise rather than silently guessing a position. The tool also pushes back on generic AI marketing copy in subject/bodyText: no stock hype words, no reflexive three-item lists, and subject/bodyText combined are capped at 1 em dash and 1 exclamation mark, with the call rejected outright over that rather than just discouraged. blocks is optional and adds anything beyond a single text/button pair: images, dividers, social icons, a multi-column layout, video, a countdown, a product card, a menu, or raw HTML, inserted between the bodyText block and the button block in the order given. See "Block types" below for the full shape; a broadcast can be built from blocks alone, with bodyText/buttonText left out entirely. Doesn't send or schedule anything; that's a separate, confirm-gated step. The response includes a previewUrl into the dashboard, but the real way to show a user what got created is peleka_get_broadcast_preview_html, not that link.
Block types
blocks (on peleka_create_broadcast, each email step of peleka_build_automation, and each variant of peleka_create_email_session) accepts any of 12 block types, plus 2 more (columns, section) that can only appear at the top level and hold the other 12 as children, one level deep. text takes content (plain text or simple HTML, same rules as bodyText). image takes imageUrl, with optional linkUrl and altText. logo takes an optional logoUrl: leave it out and the workspace's own brand logo fills in automatically, the same default peleka_create_broadcast_from_template already uses for a template's logo slot. button takes label and url. divider and spacer take no content, just optional styles. social takes socialLinks, an array of { network, url } pairs; network has to be one of eight supported values (twitter, facebook, linkedin, instagram, youtube, tiktok, website, spotify), since anything else has no icon to render. menu takes links, an array of { label, url } pairs. video takes videoUrl, restricted to a YouTube or Vimeo link: any other host has no way to fetch a thumbnail, so the call is rejected loudly instead of the block silently vanishing from the sent email. countdown takes endsAt, an ISO 8601 datetime, for the same reason (an unparseable one would otherwise just disappear rather than render). html takes raw html, sanitized through a broader allowlist than text; prefer a structured type when one fits instead. product takes title plus optional image, price, description, buttonText, buttonUrl. columns takes 2 to 4 entries, each with an array of child blocks and an optional flex weight. section takes children, a flat array of child blocks rendered as one group. Every type accepts optional styles, and every text block's own content counts toward the same em-dash/exclamation-mark budget as subject/bodyText. Most style keys are type-specific (imageWidth, buttonColor, and so on) — each block type's own styles field lists exactly which keys it accepts and their defaults, so check that rather than an existing broadcast's blocks. backgroundColor and paddingTop/paddingRight/paddingBottom/paddingLeft (a CSS length like "16px") work on every type. Mobile-only spacing is a separate set of keys, not a variant of those — paddingTopMobile/paddingRightMobile/paddingBottomMobile/paddingLeftMobile override just the narrow-screen values without touching the desktop ones. image/logo/product/video all fall back to a visible "replace this" placeholder graphic rather than a broken image icon in the compiled email: for image/logo/product this triggers whenever imageUrl/logoUrl/image was left out entirely or given but unusable, and for video it's the thumbnail specifically, whenever one can't be derived (a non-YouTube/Vimeo host, or extraction failing) — the link to the real videoUrl still renders underneath it.
peleka_list_email_templates
Read. Lists system and workspace-saved email templates, optionally filtered by category. Pair with peleka_create_broadcast_from_template instead of composing a broadcast's blocks by hand.
peleka_list_assets
Read. Lists images already uploaded to the workspace's media library: filename, fileType, url, createdAt. If nothing in the list fits what's needed, use peleka_upload_asset to add one.
peleka_upload_asset
Write. Adds an image to the media library from a URL (imageUrl) that isn't already there — there's no way to receive a file attached directly from the user's device over MCP (the model itself has no access to the raw bytes of something a user attaches in chat, independent of any Peleka-specific limitation), so this fetches and stores whatever the URL points to instead. Check peleka_list_assets first and reuse its url/id directly when something already fits, rather than re-uploading it; if imageUrl happens to already be one of the workspace's own asset URLs, or the same external link was imported once before, this returns that existing asset rather than storing a duplicate. Accepts JPEG, PNG, GIF, or WebP, up to 5MB; an SVG is also accepted and gets converted to PNG automatically, since SVG itself isn't reliably rendered across email clients. The URL has to be genuinely public, not an internal or local address. Returns the asset's id and hosted url: pass the id as logoAssetId/heroImageAssetId to peleka_create_broadcast_from_template, or the url directly as imageUrl/logoUrl in peleka_create_broadcast's blocks.
peleka_create_broadcast_from_template
Write. Same as peleka_create_broadcast, including the audience-targeting requirement, but starts from a templateId instead of a blank body. If the template has a logo slot and logoAssetId is omitted, it's filled automatically with the workspace's own brand logo (from peleka_get_workspace_context) when one is set; pass logoAssetId only to use something else, e.g. an id from peleka_list_assets. heroImageAssetId has no such default and stays empty unless given. Nothing gets fetched or generated automatically otherwise, and heroImageAssetId only fills the first empty image slot on templates with more than one. Templates ship with a button already styled but no destination, so pass buttonUrl (and buttonText to change its label) to fill it in; same "first matching empty slot" rule applies.
peleka_get_broadcast
Read. Gets the full stored details of one draft or sent broadcast (broadcastId): status, subject, sender, audience targeting, every content block, and styling. Call this before peleka_update_broadcast whenever the current content matters, since updating blocks always replaces the whole array rather than merging into it: preserving what's already there while adding to it means reading it here first and folding the existing blocks into the new array sent to the update.
peleka_update_broadcast
Write. Edits an existing draft broadcast (broadcastId); only drafts can be updated, the call is rejected otherwise. Every field is optional and touches only what's given: an omitted field is left exactly as it was, never cleared. The one exception is content, since providing any of bodyText/highlightText/buttonText/buttonUrl/blocks replaces the entire content with a freshly composed set, the same way peleka_create_broadcast builds it, not a merge. Same idea for audience: targetSegmentId/targetTagId/confirmedAllContacts only change targeting when at least one is given, and omitting all three leaves the current audience untouched. Subject-line A/B settings live in peleka_configure_ab_test, not here, and this never sends or schedules anything on its own. When a call changes anything that shows up in the compiled email — subject, previewText, or any of the content fields above — the tool's own instructions tell the model to follow up with peleka_get_broadcast_preview_html and render it as an artifact, the same as after peleka_create_broadcast, so an edit shows up without the user having to ask for it. A call that only touches name, the sender, or audience targeting skips that, since none of those change what the compiled email looks like.
peleka_get_broadcast_preview_html
Read. Returns the fully rendered HTML for one broadcast (broadcastId), compiled the same way a real send is. Works on drafts, sent broadcasts, and individual variants from peleka_create_email_session. Unfilled personalization tags show up blank in the output, which is expected for a preview, not a bug. The returned html is meant to be embedded as-is (e.g. an iframe with it as srcdoc) rather than re-typeset into a client's own markup; doing the latter can quietly drift from what the real send actually renders.
peleka_test_send_broadcast
Write. Sends a real test copy of a broadcast (broadcastId) to one email address, compiled exactly like a real send would be. Never reaches the actual audience and needs no confirmation token, since the given address is the only place it goes and doesn't need to be an existing subscriber. Capped at 5 test sends per hour, shared across every broadcast in the workspace. Requires a subject and at least one content block first.
peleka_create_email_session
Write. Generates up to three tone variants (bold, warm, minimal) of one email and saves each as its own draft broadcast, sharing a sessionId. Useful when a user wants to compare a few takes on the same campaign before committing to one. Each variant can independently set buttonText/buttonUrl, highlightText, and blocks (see "Block types" above), same as peleka_create_broadcast; the same targetSegmentId/targetTagId/confirmedAllContacts requirement applies once, for the whole session, not per variant. Unselected variants expire automatically after 30 days if nobody picks one.
peleka_select_email_variant
Write, idempotent. Marks one variant (sessionId + variantId) from a peleka_create_email_session call as the chosen draft. The other variants in that session are left to expire on their own; nothing needs to be done to discard them.
peleka_configure_ab_test
Write, idempotent. Turns an existing draft broadcast into a subject-line A/B test: subjectB against the broadcast's existing subject, split across testGroupPct of the audience, winner (by opens, clicks as tiebreak) decided after winnerAfterHours and sent to the rest. Only the subject line is tested; the body is identical for both variants, so this can't compare two genuinely different emails.
peleka_preview_broadcast_action
Read. The first half of the confirm-gated send flow. Resolves the real recipient count for one broadcast (broadcastId) and, if scheduledAt is provided, previews scheduling instead of an immediate send. Returns a confirmationToken valid for 5 minutes. A recipientCount of 0 is real, resolved fresh against the workspace's actual contacts, not stale; the response includes a warning telling the caller to say so plainly and check rather than invent a reason.
peleka_send_broadcast
Write, destructive, confirm-gated. Sends a broadcast immediately. Requires broadcastId and the confirmationToken from a matching peleka_preview_broadcast_action call.
peleka_schedule_broadcast
Write, destructive, confirm-gated. Schedules a broadcast for a future scheduledAt. Same confirmationToken requirement as peleka_send_broadcast.
Automations
peleka_list_automations
Read. Lists automations in the workspace. Filter by status.
peleka_build_automation
Write. Builds a full automation from a trigger plus an ordered sequence of wait/email/tag/condition steps: the closest thing to describing an automation in plain language and having it assembled. An email step's bodyText supports the same buttonText/buttonUrl/highlightText (and optional blocks, see "Block types" above) as peleka_create_broadcast, and fromEmail must be a verified sender the same way. A condition step branches into onTrue/onFalse sub-sequences and must be the last step in whatever list it's in, since there's no way to merge two branches back into a single continuation afterward. Always created paused; it never starts enrolling contacts on its own.
peleka_get_automation
Read. Gets the full stored details of one automation (automationId): status, trigger, the complete node/edge graph, re-enrollment policy, excluded segments, and activeEnrollments (how many contacts are currently in it). Call this before peleka_update_automation to see the current trigger/steps: updating the flow always replaces it entirely, so preserving most of it while changing one part means reading it here first.
peleka_update_automation
Write. Edits an existing automation (automationId). name/reEnrollment/excludedSegments/waitAmount/waitUnit are independent partial updates, each touched only when given. triggerType and steps are a different kind of field: they're a single unit, and providing either replaces the automation's entire flow with a freshly built one (same shape as peleka_build_automation), so both are required together whenever the flow changes at all, never a merge with what's already there. Editing an active automation's steps can strand contacts sitting at a step that no longer exists in the new flow; if that would happen, this returns requiresConfirmation: true with the at-risk count instead of applying anything, and only proceeds on a resend with confirm: true after the user explicitly accepts it. New draft broadcasts are created for any email steps in the new flow, same as peleka_build_automation; broadcasts from the previous flow's email steps aren't deleted, they're left as orphaned drafts.
peleka_activate_automation
Write, idempotent. Turns a draft or paused automation (automationId) active, so it starts enrolling contacts.
peleka_pause_automation
Write, idempotent. Pauses an active automation (automationId). Contacts already enrolled stay where they are and pick back up if it's reactivated; no new contacts enroll while paused.
peleka_enroll_contact
Write. Manually enrolls one contact (contactId) into one active automation (automationId), starting from wherever its trigger connects to. Subject to the same re-enrollment policy as automatic, trigger-driven enrollment, so calling this on someone already active in the automation doesn't create a second parallel enrollment if the policy disallows it.
peleka_unenroll_contact
Write, destructive, idempotent. Cancels one contact's active enrollment (contactId + automationId). Errors if they don't have one.
Forms
peleka_list_forms
Read. Lists published forms in the workspace, each annotated with submissionsThisWeek and submissionsThisMonth.
peleka_get_form_analytics
Read. Conversion analytics for one form (formId): views, submissions, conversion rate over time, traffic sources, device breakdown. range defaults to the last 30 days; pass range: 'custom' with from/to (ISO dates) for a specific window.
peleka_get_form_submissions
Read. Lists one form's submissions (formId), newest first, page/limit paginated: each includes the submitted field data plus the linked contact's email/status when the submission created or matched one.
Workspace & analytics
peleka_get_workspace_context
Read. No parameters. A one-shot snapshot for orienting before writing or sending anything: brand name/colors/logo/industry, the physical mailing address and unsubscribe handling required in every footer, segments with their counts, total subscriber count, and the last several sent campaigns' subject lines. There's no computed "brand voice" score in the response; the intent is to read recentCampaigns and match their tone directly. The address and unsubscribe link get injected into every send automatically, so nothing here needs to be written into a broadcast's body by hand.
peleka_get_analytics_overview
Read. High-level analytics for the workspace: contact growth with period-over-period comparison, broadcast engagement, automation completion, forms conversion. Optional startDate/endDate.
peleka_get_send_readiness
Read. Combines domain warmup phase, remaining daily send volume, and deliverability health into a go/no-go recommendation for sending right now. Pass segmentId or tagId to size the recommendation against a specific audience instead of just reporting domain status in the abstract.
peleka_get_optimal_send_time
Read. No parameters. Recommends a day-of-week and hour based on when this workspace's subscribers have actually opened past emails — real history, not machine-triggered prefetch opens. Returns hasEnoughData: false rather than guessing if the workspace hasn't sent enough yet to make the recommendation meaningful.