analytics.issue_export_csv
ChatGPTExport issue performance and poll analytics as CSV
analytics.issue_export_performance_csv
ChatGPTExport issue performance-only analytics as CSV
analytics.issue_export_polls_csv
ChatGPTExport issue poll-only analytics as CSV
analytics.issue_performance
ChatGPTFetch open/click delivery analytics for an issue
analytics.issue_trend
ChatGPTFetch daily open/click trend buckets for an issue
analytics.latest_summary
ChatGPTFetch latest issue analytics snapshot for a publication
analytics.poll_results
ChatGPTFetch poll vote totals for a specific issue
api_key.create
ChatGPTCreate an API key (PAT). Requires the calling token to hold settings:write AND every scope the new key would grant. The token value is returned ONCE — store it securely. 'full_access' grants all scopes; 'sending_access' grants issue read/write/send only. Pass mode 'test' for a key whose sends are simulated rather than delivered.
api_key.current
ChatGPTDescribe the key this connection uses: its team, the publication it is scoped to and that publication's Mailtea-managed receiving domain, its scopes, whether it is live or test, and the team's plan limits (daily_limit and monthly_limit are hard limits, null means none; ramp is the new-team daily limit while it lasts). Needs no scope.
api_key.list
ChatGPTList the team's API keys (token values are never returned).
api_key.revoke
ChatGPTRevoke (delete) an API key by its ID.
auth.me
ChatGPTReturn auth identity for current token
automation.archive
ChatGPTArchive an automation. In-flight runs are canceled unless cancel_runs is false. Returns canceled_runs. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.create
ChatGPTCreate an automation (a triggered email journey graph) in draft status. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API. Trigger types: contact.created, contact.subscribed, contact.unsubscribed, topic.subscribed, topic.unsubscribed, event, email.opened, email.clicked, email.received — topic.subscribed and topic.unsubscribed take trigger_key = topic id, event takes trigger_key = event name. Failures come back as coded issues[], not zod errors; pass validate_only to rehearse a graph before committing it. Two machine-readable resources back these tools: mailtea://automations/step-types is the catalog of trigger types, step types, config shapes, branch labels, limits and validation codes; mailtea://automations/condition-fields is the rule DSL a condition step's config.rule and any filter must follow: operators, addressable field namespaces, value references and the rule node shapes themselves. Both describe what a branching graph may contain.
automation.delete
ChatGPTPermanently delete an automation. Deleting an ACTIVE automation is refused with automation_active and its active_run_count — pause or archive it first. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.disable
ChatGPTPause an automation: it stops enrolling new contacts. In-flight runs are LEFT RUNNING unless cancel_runs is true. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.enable
ChatGPTActivate an automation so it starts enrolling contacts. Refused with coded issues[] when the graph has errors, except an unknown_step_ref at a config.* path or a trigger missing_branch that the version it last ran on already had (pre_existing: true); a never-started draft is blocked by those too. Also refused with code no_verified_sender when a send_email step has no sender it can send from: reason is NO_SENDER (no step sender, no publication default, no template from_address), DOMAIN_NOT_VERIFIED, WRONG_PURPOSE, DKIM_NOT_VERIFIED, INVALID_FROM, CUSTOM_DOMAIN_REQUIRED or BUILT_IN_SENDER, and steps[] names every blocking step key. Add a sender or verify its sending domain, then enable again. Two reasons are Mailtea Cloud only, because an automation emails contacts and some sends only reach the team's own members: CUSTOM_DOMAIN_REQUIRED means the team has not verified a sending domain of its own, so verify one (domain.create with purpose 'email', then domain.verify); BUILT_IN_SENDER means the step sends from the built-in {slug}.mailtea.email address, so give it a sender or template From on the team's verified domain. Then enable again. There is deliberately no automation.test tool: a test run sends real, billed email, so it is not exposed to agents. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.get
ChatGPTGet one automation with its full graph (steps, connections) plus valid and issues[]. There is deliberately no automation.test tool — a test run sends real, billed email, so it is not exposed to agents. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.list
ChatGPTList automations for a publication. List items omit steps, connections, valid and issues — call automation.get for the graph. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.metrics
ChatGPTPer-step performance for an automation: run totals plus entered/succeeded/failed/skipped/waiting per step, branch splits for condition and wait_for_event, and email counters for send_email. Test runs are excluded. Metrics are keyed by step_key, so renaming a step orphans its history. READING THE RESPONSE — three points, each of which otherwise produces a confidently wrong answer: - version vs graph_version. version/version_id say what the numbers are SCOPED to, and are NULL whenever no version was passed, because the aggregate then spans every version. graph_version/graph_version_id say only which graph supplied the step LABELS (the live version). State the scope from version, and say "all versions" when it is null — quoting graph_version as the scope captions combined v1+v2 traffic as a single version. - steps[] is keyed on (step_key, step_type), NOT on step_key alone. A key deleted as one step type and later re-added as another appears as two entries with the same step_key in an all-versions aggregate. They are different steps, even though they share a key. - email.delivered means CURRENTLY delivered — accepted and not subsequently bounced — so delivered + bounced never exceeds sent. It is not a running total of everything ever accepted. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.preview_step
ChatGPTRender what a send_email step would send, using the template's PUBLISHED version and the worker's own variable resolution. Nothing is sent. Returns {subject, html, text, variables, warnings[], variant {matched, forced, available}}; variables maps names to {value, source}: event, contact, steps, literal, default or missing. A config.variables value is a literal string or {"var": "event.properties.plan", "default": "friend"}. A mistyped var path resolves to nothing silently, so source "missing" or ANY warning means fix it before automation.enable. Warning codes: variable_unresolved, variable_unmapped, mapping_unused (carry key; variable_unresolved can show source "default"), subject_empty (a send would 422), variant_rule_needs_run (force the variant), contact_not_active (contact unsubscribed/suppressed). Pass event for event.properties., contact_id for contact. (needs contacts:read; key needs templates:read). Pass trigger {type, key: step key} to seed steps.<key>.event. Errors: 400 variant_not_found, 404 contact_not_found, 400 unsupported_step_type, 404 template_not_found, 422 template_not_published.
automation.update
ChatGPTUpdate an automation. Pass only the fields to change; passing steps replaces the whole graph and cuts a new version. Fields you omit keep their STORED value, so reentry_window_seconds must be sent as null to clear it. Switching reentry_policy from once_per_window to once or always without doing so fails with "reentry_window_seconds is only valid when reentry_policy is once_per_window" on this and every later call. Saving is never blocked for draft/paused/archived automations: issues ride along informationally. A graph update to an ACTIVE automation is refused with 422 active_graph_invalid only when it adds an error the live version does not already have (issues[] lists just those new ones; issues already live carry pre_existing: true and do not block). Changing the trigger (trigger_type or trigger_key) of an ACTIVE automation is refused with 422 trigger_locked_while_active: pause it first, then change the trigger. Pass base_version with steps (the version from your last read) so your graph never overwrites steps someone saved since; on a 409 stale_version, re-read with automation.get and retry. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API. Two machine-readable resources back these tools: mailtea://automations/step-types is the catalog of trigger types, step types, config shapes, branch labels, limits and validation codes; mailtea://automations/condition-fields is the rule DSL a condition step's config.rule and any filter must follow: operators, addressable field namespaces, value references and the rule node shapes themselves. Both describe what a branching graph may contain.
automation.validate
ChatGPTValidate a graph without creating anything: the standalone dry run for a graph with no automation in existence yet. Returns {valid, issues[]} with the same coded issues a create or update failure returns. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API. Two machine-readable resources back these tools: mailtea://automations/step-types is the catalog of trigger types, step types, config shapes, branch labels, limits and validation codes; mailtea://automations/condition-fields is the rule DSL a condition step's config.rule and any filter must follow: operators, addressable field namespaces, value references and the rule node shapes themselves. Both describe what a branching graph may contain.
automation.version
ChatGPTGet one historical graph version in full, including its steps and connections. This is the graph a run pinned to that version is actually executing, so read it — not the live automation — when explaining what an in-flight or completed run did. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation.versions
ChatGPTList an automation's graph versions, newest first — version number, graph_hash, trigger, is_live and created_at, without the graph itself. Versions are not cosmetic: an in-flight run is PINNED to the version it started on, so updating steps cuts a new version and leaves every running contact executing the old graph. Call automation.version for one version's steps and connections. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation_run.cancel
ChatGPTCancel one in-flight run. Returns the run in full. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation_run.get
ChatGPTGet one run in full: the PINNED graph it is executing (not the live one), current step, waiting state, last error, and per-step step_runs. A step_run whose output carries recorded_after_run_ended: true finished AFTER the run itself ended — the run was canceled, archived, or the contact unsubscribed while that step was in flight. Its completed_at is legitimately later than the run's own, and the side effect really happened (the email was sent and billed), so it is not an error and not a data glitch. The run did not resume, and no automation.step.completed webhook fired for it. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
automation_run.list
ChatGPTList runs of an automation. Each run pins the graph version it started on. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
contact.delete
ChatGPTPermanently delete a contact by its ID or email address.
contact.get
ChatGPTGet a single contact by its ID or email address.
contact.get_properties
ChatGPTRead a contact's custom property values.
contact.import_csv
ChatGPTImport contacts from CSV text. Only the email column is read: names and any other columns are not imported, and the result lists them in ignoredColumns. To store a name or another field, import first, then call contact.set_properties per contact. Returns per-outcome counts plus enrolledAutomations, the number of automation enrollments the import created (always 0 unless enrollInAutomations is true).
contact.list
ChatGPTList contacts for a publication
contact.referral_milestone_remove
ChatGPTDelete a referral milestone rule
contact.referral_milestone_upsert
ChatGPTCreate or update a referral milestone rule
contact.referral_milestones
ChatGPTList referral milestone rules for a publication
contact.referral_rewards
ChatGPTList awarded referral rewards for a publication
contact.referral_summary
ChatGPTLoad referral conversion leaderboard for a publication
contact.set_properties
ChatGPTSet a contact's custom property values — the data behind {{contact.<key>}} merge tags. Defining a property (contact_property.create) only creates the field; this puts a value on a contact. Identify each value by key (the name you write in the template) or by propertyId — exactly one. An empty value CLEARS the property, which makes its fallback_value apply again on the next send.
contact.set_status
ChatGPTSet contact status (active, unsubscribed, suppressed)
contact.upsert
ChatGPTCreate or reactivate a contact by email
contact_property.create
ChatGPTDefine a custom contact property (custom field) for the team. Property values are set per-contact via contact.set_properties.
contact_property.delete
ChatGPTDelete a custom contact property definition.
contact_property.list
ChatGPTList the team's custom contact property definitions.
contact_property.update
ChatGPTUpdate a custom contact property's fallback value or description.
domain.claim
ChatGPTClaim a domain that another publication currently holds. It is for a domain that domain.create refused with code 'domain_held_elsewhere'. Returns one TXT record in 'records': the operator publishes it in the domain's DNS to prove they control it, then call domain.claim_verify. The claim expires if it is not verified within 72 hours. Completing a claim releases the other publication's domain: their sending stops, and they are notified by email that the host was released and told how to claim it back. A claim completes only for someone who can publish the TXT record in the domain's DNS.
domain.claim_cancel
ChatGPTWithdraw a pending domain claim. Only a pending claim can be cancelled; a completed or failed one returns code 'claim_not_pending'.
domain.claim_get
ChatGPTPoll a domain claim. 'status' is 'pending', 'completed' or 'failed'; a failed claim carries a machine-readable 'failure_reason' and a completed one carries 'domain_id', the new domain it created.
domain.claim_verify
ChatGPTCheck the claim's TXT record and complete the claim if it is there. Safe to call repeatedly: a record that has not propagated yet returns code 'claim_txt_not_found' and leaves the claim pending with the SAME record, so nothing has to be republished. On success the previous holder's domain is released — they are emailed that the host went and how to claim it back — and a new sending domain is created for this publication, returned in full as 'domain' so its DNS records can be published without a second call.
domain.create
ChatGPTRegister a domain for a publication: a sending domain (purpose 'email', or 'both' to also serve the site) or a website domain (purpose 'site', the default). An omitted purpose makes a 'site' domain, which cannot send email, so pass purpose 'email' for a domain you want to send from. Returns the DNS records (in 'records') the operator must add. Each row's 'record' names what it is for (Ownership, DKIM, SPF, MX, Return-Path, Tracking), 'type' is the DNS type, and 'status' is that record's own state. Set purpose to 'email' (or 'both') to use it as a sending 'from' domain; both the ownership TXT and the DKIM TXT must verify before the domain can send. Pick the region closest to your recipients: it is fixed at creation, and moving a domain means deleting and re-adding it.
domain.delete
ChatGPTRemove a domain from a publication.
domain.get
ChatGPTGet a single domain including the DNS 'records' to add for verification.
domain.list
ChatGPTList email/site domains for a publication, optionally filtered by region or status.
domain.tracking_create
ChatGPTAdd a tracking sub-domain (CNAME) under a domain — used to serve open-pixel/click-tracking links from your own domain. Returns the CNAME record to add, then call domain.tracking_verify.
domain.tracking_delete
ChatGPTRemove a tracking sub-domain.
domain.tracking_list
ChatGPTList tracking sub-domains for a domain.
domain.tracking_verify
ChatGPTVerify a tracking sub-domain by checking its CNAME record; status becomes 'verified'. It removes every other tracking subdomain on the domain and detaches their edge hosts.
domain.update
ChatGPTUpdate a domain's purpose (email/site/both), primary flag, tracking policy, TLS policy, tracking subdomain, or custom return-path. A domain's REGION cannot be changed — delete it and add it again in the new region. Turning open_tracking or click_tracking off stops Mailtea measuring opens or clicks for EVERY message from this domain — a single send cannot re-enable it. Setting custom_return_path delegates a subdomain as the envelope sender so SPF aligns with this domain; it requires two DNS records and reports back in the domain's records list. Pass tracking_subdomain: null to REMOVE a tracking subdomain — this is not reversible for links already sent.
domain.verify
ChatGPTCheck a domain's DNS and report its verification state. Sending is gated on two parts: the ownership TXT record must verify (which sets status to 'verified') AND the branded DKIM TXT record must verify. Ownership verification alone does NOT make a domain sendable. The response now includes 'dkim_status' and 'receiving_mx_found' so you can confirm both before sending. Verify is also what settles the MX row in 'records': the answer is stored, so every later read of the domain reports what this verify found rather than 'pending'. The response also carries 'receiving_identity_status' (pending, verified, failed, or null when not started): whether the domain is registered to RECEIVE mail at Mailtea's inbound endpoint. An MX record pointing at Mailtea only receives mail once it reads 'verified'.
email.analytics
ChatGPTAggregate transactional email metrics over an optional date window: totals, delivered/bounced/opened/clicked counts, per-status counts, and delivery/open/click/bounce rates. Delivery and bounce rates are measured against everything sent; open and click rates exclude bounced mail, which was never open-able.
email.batch
ChatGPTSend up to 100 transactional emails in one request. Each item is shaped like email.send but WITHOUT attachments or scheduled_at. Returns the ids in request order. The send allowance is measured against the WHOLE batch, not per message: if the batch does not fit in what is left, the request is refused with 403 and NOTHING is created — the error names how many emails the batch needed and how many of the limit are already used. Re-sending the same batch fails identically, so split it into smaller batches or wait for the limit to reset. The recipient restriction applies here too: until the team verifies a sending domain, one item addressing anyone but a verified team member refuses the WHOLE batch with 403 and reason 'system_domain_recipient_restricted' — as does any item sent from the built-in '{slug}.mailtea.email' address.
email.cancel
ChatGPTCancel a scheduled transactional email before it sends.
email.get
ChatGPTRetrieve a transactional email by id with its delivery status (last_event), the reason it failed if it did (error, failed_at), tracking counters (open_count, click_count), and dropped_recipients: anyone the message did not reach and why. to/cc/bcc are what was ASKED for; a partially-delivered send looks identical to a fully-delivered one unless you read dropped_recipients. Also returns mode; a test-mode row is marked [test] in the summary and was never delivered. Also returns message_id: the Message-ID header recipients see, in angle brackets, null until the email is sent.
email.inbound_get
ChatGPTRetrieve a single inbound email by id (rxemail_): headers, html and text body, a signed download URL for the raw message, and its attachments. A body over 20,000 characters is cut with a marker; the raw message has all of it. Tenancy is resolved from the id, so no publicationId is needed. It also returns reply_to (where a reply goes by default), the checks run on arrival (spam_verdict, virus_verdict, spf_verdict, dkim_verdict, dmarc_verdict: PASS, FAIL, GRAY, PROCESSING_FAILED or null) and attachments_withheld (true when the files were kept back after a failed virus check).
email.inbound_get_attachment
ChatGPTGet a single inbound-email attachment (rxatt_) with a signed download URL. Tenancy is resolved from the parent email id, so no publicationId is needed.
email.inbound_list
ChatGPTList inbound (received) emails for a publication, most recent first; paginate with limit/cursor. Only this inbound tool needs publicationId — email.inbound_get/list_attachments/get_attachment/reply resolve tenancy from the email id.
email.inbound_list_attachments
ChatGPTList the attachments of an inbound email, each with a signed download URL. Tenancy is resolved from the email id, so no publicationId is needed.
email.inbound_reply
ChatGPTReply to an inbound email. It threads automatically: In-Reply-To and References are set by the server from the original. It goes to the Reply-To when that is exactly one valid address, else the From when that is exactly one valid address, else 422 no_reply_target; pass to to name the From or a single-address Reply-To entry (anyone else is refused with 422 reply_target_not_allowed). On Mailtea Cloud, a team with no verified sending domain may reply to the person who wrote (the original's From) or to its own verified members, so answering a customer works, but cc and bcc get no such allowance, and a reply to a Reply-To that differs from the From is refused (403, reason/code system_domain_recipient_restricted); verify a domain to lift all of it, or pass to with the From. Provide html and/or text. Omit from to send from the verified domain the mail was delivered to. Attach files with attachments (up to 20, 6 MiB together; over the hosted Mailtea connector the whole request is limited to about 1 MB, while the local mailtea-mcp server takes 6 MiB). headers may set only Auto-Submitted and X-Auto-Response-Suppress. A plan limit answers 403 with reason daily_cap, monthly_cap or ramp_cap. Tenancy is resolved from the email id (no publicationId). Returns the resulting transactional email id (txemail_) and its status; email.get shows the Message-ID once it is sent.
email.lint
ChatGPTCheck email HTML against the Can I Email support matrix for the clients Mailtea refuses to regress (Apple Mail, Gmail, Outlook desktop). Returns {findings:[{slug, severity, feature, clients}], failCount, warnCount, strictClients, linted}. severity 'fail' means the layout BREAKS when unsupported (flex/grid collapse, absolute positioning, CSS variables, viewport units); 'warn' means it degrades gracefully (a gradient or shadow simply does not paint; a color-mix() color is dropped by Outlook desktop, so use a plain hex). It catches layout problems that are otherwise only visible in a rendered preview. Pass exactly one of issueId (lint what is saved) or html (lint before you post it).
email.list
ChatGPTList transactional emails (most recent first). Filter by status, mode (live or test), tags, or a search substring over recipient/sender/subject; paginate with limit/offset. Returns id, status, subject, recipient per email; test-mode rows are marked [test].
email.reschedule
ChatGPTReschedule a still-scheduled transactional email to a new time.
email.resend
ChatGPTResend a failed or bounced transactional email — creates a fresh copy with the same content. Only emails whose status is 'failed' or 'bounced' can be resent; others return an error.
email.send
ChatGPTSend a transactional email to one or more specific recipients. Provide inline content (html and/or text) OR a template reference — not both. Unlike issue.send_now (which sends a newsletter to the WHOLE publication list), this delivers a one-shot email to the exact addresses in 'to'. Set scheduled_at to send later. Returns the new email's id. ON MAILTEA CLOUD, UNTIL THE TEAM VERIFIES A SENDING DOMAIN OF ITS OWN, this only delivers to verified members of that team (a self-hosted install is exempt) — whatever 'from' you use. Any other recipient in to/cc/bcc is refused with 403 and reason 'system_domain_recipient_restricted'. Verifying one domain (domain.create, then domain.verify) lifts it for the whole team; the built-in '{slug}.mailtea.email' address stays team-only even then.
event.send
ChatGPTIngest a custom event. Enrolls contacts into automations triggered on that event name and resumes runs waiting for it. Provide exactly one of contact_id or email. Idempotent on idempotency_key: a replay returns the ORIGINAL event id with enrolled_automations: 0, resumed_runs: 0, replayed: true. resumed_runs: 0 does not prove nothing matched — read the run, not the counter. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
event_definition.create
ChatGPTDeclare an event name and its optional property schema so operators and agents can discover it. schema_json is a Mailtea event schema document, NOT JSON Schema: the reflex attempt {"type": "object", "properties": {...}, "required": [...]} is refused with invalid_event_schema (Unknown schema key "type", Unknown schema key "required") — "type" and "required" belong on each PROPERTY, never at the top level. The only top-level keys are "properties" (an object keyed by property name, max 200) and "additional_properties" (boolean, DEFAULT TRUE — a schema documents what it knows about without closing the payload; set it false to reject undeclared keys). Each declared property accepts only {type?, required?, description?}: "type" is one of string, number, boolean, object, array, null — or an ARRAY of those, the same shape event_definition.get returns in inferred_properties; "required" is a boolean; "description" is a string. Example: {"properties": {"plan": {"type": "string", "required": true, "description": "Plan the contact bought"}, "seats": {"type": ["number", "null"]}}, "additional_properties": false}. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
event_definition.delete
ChatGPTDelete an event definition. This removes the DECLARATION only: past events are not deleted and ingest is not stopped — the next event of this name recreates the definition with source "auto", losing the description and schema_json. To stop enforcing a schema while keeping the declaration, call event_definition.update with schema_json: null instead. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
event_definition.get
ChatGPTGet one event definition, including inferred_properties computed over the last 500 events. Watch the coverage figure: a condition on a key present in 3% of events will almost never match. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
event_definition.list
ChatGPTList declared event definitions for a publication. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
event_definition.update
ChatGPTUpdate an event definition's description or schema. The event name is immutable — renaming is refused with event_name_immutable. Omitting schema_json leaves the stored schema untouched; passing schema_json: null CLEARS it and returns the event to free-form. schema_json is a Mailtea event schema document, NOT JSON Schema: the reflex attempt {"type": "object", "properties": {...}, "required": [...]} is refused with invalid_event_schema (Unknown schema key "type", Unknown schema key "required") — "type" and "required" belong on each PROPERTY, never at the top level. The only top-level keys are "properties" (an object keyed by property name, max 200) and "additional_properties" (boolean, DEFAULT TRUE — a schema documents what it knows about without closing the payload; set it false to reject undeclared keys). Each declared property accepts only {type?, required?, description?}: "type" is one of string, number, boolean, object, array, null — or an ARRAY of those, the same shape event_definition.get returns in inferred_properties; "required" is a boolean; "description" is a string. Example: {"properties": {"plan": {"type": "string", "required": true, "description": "Plan the contact bought"}, "seats": {"type": ["number", "null"]}}, "additional_properties": false}. Arguments are snake_case (publication_id, automation_id), matching the Mailtea REST API.
issue.apply_ops
ChatGPTApply a batch of declarative edits to a draft email, the surgical alternative to issue.update_draft, which replaces the whole document. Use this to change one button's colour, rewrite a paragraph, reorder sections, or restyle the email without regenerating it. Every op is applied in order and answered with a report: {applied, skipped:[{opIndex, reason, path, detail}]}. A success with skips is the normal, honest outcome. The report is the only place a refused edit is named. Reasons: invalid_op, unknown_path, stale_address, unknown_block_kind, unknown_attr, bad_attr_value, unknown_style_token, bad_index, not_a_container, cycle, empty_edit, value_too_long (refused, never truncated), doc_full, not_email_safe, out_of_scope, internal_error. A structural op returns a fresh outline in the report, and every path you held may have moved. The outline and updatedAt come from issue.get_editor.
issue.create_draft
ChatGPTCreate a marketing email draft. Set kind to 'newsletter' (default) for recurring content that can publish to the public site, or 'broadcast' for a one-time email-only send (promotion, launch, announcement). Provide content one of three ways: templateId (seed from a published server template's PUBLISHED version; use template.list/template.get to find one, and template.publish first if it has unpublished changes), contentHtml (raw HTML), or contentSpec (json-render spec). Spec is recommended for AI agents; use components: Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown, MailteaHeader, MailteaFooter, MailteaSpacer, MailteaContentBlock. Seeding from a template fills in the variables you pass (both {{key}} and Visual Email Designer {key} forms) and leaves everything else for the broadcast to fill per recipient: a declared variable you do not pass keeps its fallback_value for recipients with no value, and undeclared tokens like {{contact.first_name}} are left as they are. The draft keeps the template's published page style, is wrapped in that page when it is sent, and has its show-if blocks decided per recipient then. The draft's subject is always the title you pass here, never the template's own subject line. The template's preview text is part of its rendered HTML and does reach the inbox, but the draft's own preview text field stays empty. Pass segmentId to send to one audience segment instead of all active contacts.
issue.delivery_progress
ChatGPTFetch send progress snapshot for an issue
issue.get_editor
ChatGPTLoad the editor payload for a draft or scheduled issue. Returns outline (the document as a flat list of addressable blocks, {path, type, text}), styles (current style tokens), headers (subject, previewText), docBacked, and updatedAt. The outline paths are the addresses issue.apply_ops takes, and updatedAt is its baseUpdatedAt value. docBacked: false means the draft holds raw HTML, not an editable document, so only a compose op can edit it.
issue.list_recent
ChatGPTList a publication's issues, most recently updated first. Page with limit and offset.
issue.preview
ChatGPTPreview newsletter HTML for an issue. HTML over 20,000 characters is cut with a marker.
issue.preview_draft
ChatGPTPreview newsletter HTML and text for unsaved draft content. Each over 20,000 characters is cut with a marker.
issue.publish_to_web
ChatGPTPublish an issue to the publication's public website, making it readable on the web (typically a sent issue). Newsletters only: a broadcast is email only, and publishing one is refused.
issue.remove_draft
ChatGPTDelete an existing draft issue
issue.schedule
ChatGPTSchedule an issue for delivery. A newsletter is also published to the public website when it has been delivered; a broadcast is email only and never is. A scheduled newsletter cannot be sent by email only: use issue.send_now with publishToWeb false for that. One send reaches at most the server's recipient limit, 25,000 by default. A larger audience is refused with the count when you schedule, and a scheduled post whose audience grows past the limit before it goes out fails with the count. Nothing is sent either way: split the audience into segments and send one post to each. The send is refused, with the reason, when the team has no verified sending domain and the audience includes anyone outside the team.
issue.send_and_wait
ChatGPTSend an issue now, as issue.send_now does, and poll until sent/failed or timeout. A newsletter is also published to the public website unless publishToWeb is false; a broadcast never is. The same recipient limit applies: one send reaches at most 25,000 contacts by default, and a larger audience is refused with the count.
issue.send_now
ChatGPTSend an issue immediately. A newsletter is also published to the public website unless publishToWeb is false (Studio's Email only); a broadcast is email only and never is, whatever publishToWeb says. One send reaches at most the server's recipient limit, 25,000 by default. A larger audience is refused with the count and nothing is sent: split the audience into segments and send one post to each. The send is refused, with the reason, when the team has no verified sending domain and the audience includes anyone outside the team.
issue.send_test
ChatGPTSend a TEST copy of a newsletter draft to specific recipients (yourself/teammates) to check it before subscribers see it. Renders the issue exactly as a subscriber would receive it and delivers it as a one-shot email with a '[TEST]' subject prefix. This does NOT send to the publication's audience — use issue.send_now for the real send. Until the team verifies a sending domain of its own — and always with a built-in '{slug}.mailtea.email' from — every recipient must be a verified member of the team, or the send is refused with 403 and reason 'system_domain_recipient_restricted'.
issue.unpublish_from_web
ChatGPTRemove an issue from the publication's public website.
issue.unschedule
ChatGPTCancel an issue schedule and move it back to draft
issue.update_draft
ChatGPTUpdate an existing draft issue. Only the fields you pass change: pass title to change the subject, name to rename the post, from/replyTo to set its sender headers, segmentId to choose its audience (null sends it to all active contacts again), or contentHtml/contentSpec to replace the whole document (use issue.apply_ops for targeted edits). Pass baseUpdatedAt (from issue.get_editor) so your write never overwrites a change someone made since; on a 'changed elsewhere' conflict, re-read with issue.get_editor and retry. A draft replaced with contentHtml stays HTML: the Visual Email Designer shows it read-only until the operator chooses Edit as blocks, and opening it changes nothing.
issue.wait_delivery
ChatGPTPoll issue send progress until sent/failed or timeout
monetize.offer_list
ChatGPTList sponsor offers for a publication
monetize.offer_remove
ChatGPTDelete a sponsor offer
monetize.offer_upsert
ChatGPTCreate or update a sponsor offer
publication.create
ChatGPTCreate a publication workspace and attach owner membership
publication.domain_list
ChatGPTList custom domains connected to a publication
publication.domain_remove
ChatGPTRemove a publication custom domain
publication.domain_set_primary
ChatGPTSet an existing publication domain as primary
publication.domain_traefik_preview
ChatGPTGenerate Traefik dynamic-file preview for publication domains
publication.domain_upsert
ChatGPTCreate or update a publication custom domain
publication.domain_verify
ChatGPTCheck the website domain's verification TXT record in public DNS and mark the domain verified when it matches. Mailtea looks the record up itself; publish the TXT name and value that publication.domain_upsert returned first.
publication.list
ChatGPTList publication workspaces available to current user/token
section.catalog
ChatGPTList shared marketplace section packs
section.create
ChatGPTCreate a reusable section snippet
section.import_pack
ChatGPTImport a section pack into the publication's reusable sections. A section whose name matches one already saved is overwritten; the rest are created.
section.list
ChatGPTList reusable editor sections for a publication
section.pack_create
ChatGPTCreate a custom marketplace pack for a publication
section.pack_remove
ChatGPTDelete a custom marketplace pack
section.pack_restore_revision
ChatGPTRestore a custom marketplace pack to a specific revision
section.pack_revisions
ChatGPTList version history for a custom marketplace pack
section.pack_update
ChatGPTUpdate a custom marketplace pack
section.remove
ChatGPTDelete a reusable section snippet
section.update
ChatGPTUpdate a reusable section snippet
segment.create
ChatGPTCreate a saved audience segment defined by filters: status_filter, query_filter and/or inactive_days (filter-based, not manual membership).
segment.delete
ChatGPTDelete an audience segment. A segment that a draft, scheduled or sending post targets cannot be deleted: the call is refused with segment_in_use and lists those posts. Point each draft at another segment with issue.update_draft (segmentId), or pass null there to send it to all active contacts; unschedule a scheduled post first (issue.unschedule). A post that is sending frees the segment when it finishes.
segment.get
ChatGPTGet a single audience segment by ID.
segment.list
ChatGPTList saved audience segments for a publication.
segment.update
ChatGPTUpdate an audience segment's name, description, or filters.
sender.create
ChatGPTCreate a named sender. The email domain must be a verified, DKIM-verified sending domain of the publication. The built-in '{slug}.mailtea.email' host is REFUSED here: it can only ever email verified members of the team, so a sender on it would be refused on every real send. Verify a domain first (domain.create).
sender.delete
ChatGPTRemove a named sender
sender.list
ChatGPTList named from-identities (senders) for a publication
sender.set_default
ChatGPTMake a sender the publication's default from-identity
sender.update
ChatGPTUpdate a sender's name, reply-to, or default flag. Email is immutable.
site.apply_ops
ChatGPTApply a batch of declarative edits to the site DRAFT, the safe way to design a site. The public site keeps serving the live version until site.publish. Every op is applied in order and answered with a report: {applied, skipped:[{opIndex, op, reason, detail}]}. A 200 with skips is the normal, honest outcome. The report is the only place a refused edit is named. Reasons: unknown_template, unknown_node, unknown_slot_key, unknown_slot_field, copy_shape_mismatch, repeat_out_of_bounds, value_too_long (refused, never truncated), bad_index, page_full, no_design, unknown_token, bad_token_value, unknown_style_prop, empty_edit, not_a_container, cycle, extract_failed, invalid_op. Section templates from site.section_templates_list can be inserted by id. A publication has exactly ONE site, made of pages. A page document is {"version":3,"sections":[...]}; a section is {"id","type":"section","blocks":[...]}. Blocks are discriminated on "type": heading, text, richText, button, link, image, embed, logo (src ""=publication logo, heightPx, href), icon, divider, spacer, subscribeForm, contactForm, unsubscribeForm, postHeader, postBody, postCollection, group (children[]), columns (columns[i].blocks[]). Every style/layout property is {"ref":"palette.accent"} (linked to the theme) or {"value":24} (a literal override). A ref stays linked to the theme, so a later theme change restyles it. Limits: 40 sections/page, 50 children per container, 200 nodes/page.
site.asset_delete
ChatGPTRemove an image from the publication's asset library so it no longer appears in the library or the picker. The stored file is kept and its URL keeps resolving, so already-sent emails do not break. Removing an asset does not edit any email, template or page that references it; update those first or they keep showing the image.
site.asset_list
ChatGPTList images in the publication's asset library, newest first, with the absolute URLs to use as an image block's src or a template's imageSrc slot. Each entry carries fileName, contentType, byteSize and width/height, so pick by what the image IS rather than by position. These URLs are hosted by Mailtea and stay valid.
site.asset_upload
ChatGPTUpload an image into the publication's asset library and get back the permanent URL to use as an image block's src. This is the ONLY way to put a picture that is not already in the library into an email, a template, or a site page: an image block needs an absolute URL, and hot-linking somebody else's host breaks the moment they move it. PNG, JPEG, GIF, WebP or SVG, 5 MB max. SVG is accepted for site pages (it is served with a sandbox policy so it cannot run script), but Gmail and Outlook do not show SVG images in email, so use PNG or JPEG for any image that goes into an email or template. The bytes must really be the format you declare. Send width/height when you know them: the editor uses them to reserve space so the layout does not jump.
site.design_brief_get
ChatGPTReturn the design brief saved for this publication's site: free text in markdown, written in Mailtea Studio or with site.design_brief_set, describing the intended brand and layout. Empty when none is saved.
site.design_brief_set
ChatGPTReplace the site's design brief with new markdown (max 10000 chars), or clear it with null. The whole brief is replaced and the previous text is not kept.
site.discard_draft
ChatGPTThrow away every unpublished draft edit across the whole site and revert to the live version. This cannot be undone, and it discards every pending edit, including edits other people made.
site.get
ChatGPTLoad the publication's website: settings, the v3 design (theme tokens, navbar, footer), the design brief, and draftVersion. The navbar and footer node ids it returns are the ones edit_copy and edit_style address. Both trees are shared by every page, so copy placed in them shows on every page, and arrange does not move them. The design brief is free text the publication's team saved to describe the site's intended look. draftVersion is the value site.apply_ops passes back as baseVersion. The page document format is described on site.apply_ops.
site.navbar_templates_list
ChatGPTThe curated navbar library: every navbar template with its id, name, description, and mobile behaviour. Apply one with the set_navbar_template op in site.apply_ops. Structural ops do not address the site chrome, and a hand-built navbar has no distribution control, so it tends to rely on fixed-width spacers that break on a phone. A template is theme-linked and carries its own mobile menu.
site.page_get
ChatGPTLoad one page with its full document, draft-coalesced (what the builder shows, not what the public site serves). Address it by pageId, slug, or kind. Node ids for site.apply_ops come from this document. The page document format is described on site.apply_ops.
site.page_upsert
ChatGPTCreate or replace a WHOLE page document. Reserved kinds are home, archive, post, subscribe, unsubscribe and unsubscribe_success. Anything else is kind "custom", at any slug that is not reserved, because the public route resolves by slug alone. This writes the page's live document, not the draft that site.apply_ops edits, so a published page changes for visitors at once. A draft page is not served until site.publish or a write with status "published". A new custom page is a draft unless status says otherwise. An existing page keeps its status when status is omitted. Building is unlimited. Published custom pages are capped by plan (free 1, hobby 5, pro 25), and an over-cap publish is refused with a message naming the limit. A total parser silently REPAIRS what it cannot accept (clamping values, dropping unknown properties and overflow past 40 sections, 50 children per container or 200 nodes), so success does not mean the document was stored as sent. site.page_get returns what was stored, unless the page has a pending draft (hasDraft): that draft is kept, site.page_get returns it, and site.publish replaces this write with it. The page document format is described on site.apply_ops.
site.pages_list
ChatGPTList the site's pages (id, kind, slug, title, status). kind is one of home, archive, post, custom, unsubscribe, unsubscribe_success — 'post' is the LAYOUT frame wrapped around every published post, not a single post. Reserved system pages are seeded on first call.
site.publish
ChatGPTPublish the site: promote every pending draft page and the draft design to live, for real visitors. Publishing makes the draft visible to every visitor. Until then the operator can preview and approve it. Refused if publishing would put more CUSTOM pages live than the plan allows (free 1, hobby 5, pro 25): the whole publish is refused rather than a subset going live, and the message names the limit. Unpublish or delete a custom page and call again.
site.section_templates_list
ChatGPTThe curated Section Library: every insertable section template with its id, category, name, description, and slots. Slots are the contract for copy: a value slot takes a string under its key, a repeat slot takes a list of item maps (its itemSlots name the per-item keys, min/max bound the count). A template with no slots is a structural scaffold, inserted as authored. site.apply_ops skips a templateId that is not in this list, with reason unknown_template.
suppression.add
ChatGPTAdd addresses to the organization's suppression list. They will be excluded from all sends across every publication.
suppression.export
ChatGPTExport the team's suppression list as CSV (columns: email, reason, source, created_at), newest first, one page at a time. Returns the CSV text in 'csv', the page's row count in 'rowCount', and 'nextCursor': pass it back as cursor for the next page, null on the last page.
suppression.remove
ChatGPTRemove addresses from the organization's suppression list. Matching contacts are restored to active/unsubscribed.
suppression.search
ChatGPTSearch the organization's suppression list (addresses that will never be emailed). Filter by reason, email substring, or creation-date window; paginate with starting_after.
template.create
ChatGPTCreate an email template. Exactly ONE content source: editor_doc, spec, or html. editor_doc (format "editor") is the same designed template the Visual Email Designer produces and the one to reach for when composing a real email. editor_doc is a TipTap/ProseMirror document, {"type":"doc","content":[ ...block nodes ]}. The server renders the email HTML from it and stores that, and refuses a request that also carries html. Every node is {"type":"...","attrs":{...},"content":[...]}. Block nodes that render: paragraph, heading (attrs.level 1-6), bulletList / orderedList / listItem, blockquote, horizontalRule, image (attrs.src, attrs.alt), button (attrs.href, attrs.variant "filled"|"outline", attrs.alignment "left"|"center"|"right", attrs.fullWidth), spacer (attrs.height "sm"|"md"|"lg"|"xl"), table, twoColumns / threeColumns / fourColumns each wrapping columnsColumn children, linkCard, logo, footer, htmlCodeBlock, section. Any block takes attrs.visibility: "all" (default), "email" (default for footer and logo) or "web". In raw HTML: <!--mailtea:visibility email|web-->...<!--/mailtea:visibility-->. Text is {"type":"text","text":"..."} carrying optional marks: bold, italic, underline, strike, code, sub, sup, textStyle, and link (attrs.href). A "subtitle" string on the doc root becomes the inbox preview text. Minimal example: {"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Hello"}]},{"type":"paragraph","content":[{"type":"text","text":"Welcome aboard."}]}]}. These node types render to NOTHING in email and their content is lost: youtube, xPost, threadsPost, codeBlock (use htmlCodeBlock for raw HTML); repeat renders its content once; a showifkey attribute hides the block for contacts whose property is empty. A document that renders to an empty email is refused with 400 editor_doc_unrenderable, and the response names the offending types in node_types. Bounds: 512 KB serialized, 256 KB per string, 40 levels deep. spec (format "spec") is the programmatic alternative for generated layouts; available components: Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown, MailteaHeader, MailteaFooter, MailteaSpacer, MailteaContentBlock. html (format "html") stores raw HTML verbatim. Whichever you use, the template ends up with stored html, so it is sendable from all three. Templates start as draft, so call template.publish before an automation or issue can use one. Sends read the PUBLISHED version (the content, From and Reply-To as of the last template.publish), never later unpublished edits.
template.delete
ChatGPTDelete an email template.
template.duplicate
ChatGPTDuplicate an email template into a new draft copy.
template.get
ChatGPTGet a single email template by ID, including its rendered html, its spec, and, for format "editor", the editor_doc design source plus style_profile, mailtea_theme and global_css. These fields are the working copy (the latest saved design), which may not be what is sending: has_unpublished_versions: true means it differs from the published version. This is the read half of editing a designed template: get it, change the doc, send it back through template.update. template.list omits all of those (a page of full documents would be a very different response size) and returns only category, preview_image_url and tags alongside the summary.
template.list
ChatGPTList email templates for a publication
template.publish
ChatGPTPublish an email template: its saved changes, including from and reply_to, become the version that sends. Only a published template can seed an issue, a post, or an automation's send_email step. Calling this on a template that is ALREADY published is how saved edits go live: editing or restoring a published template no longer publishes automatically (see template.update, template.restore_version). The change is saved with has_unpublished_versions: true, and this call is what promotes it. Reversible with template.unpublish. Pass base_revision to publish only the revision you read, never a change someone made since.
template.render
ChatGPTRender a json-render spec to email-safe HTML and plain text without creating a template — a dry run for previewing a spec before template.create. Returns { html, text }. Available spec components: Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown, MailteaHeader, MailteaFooter, MailteaSpacer, MailteaContentBlock.
template.restore_version
ChatGPTPut an older design from template.versions back onto the template, with the version's from and reply_to: the From and Reply-To come back too, including a null that clears them. A version with sender_recorded: false was recorded before versions kept the sender and leaves the current From and Reply-To as they are. ON A LIVE TEMPLATE: restoring is a content write, but it no longer returns the template to draft or stops it sending. The template stays published, the restored design is saved as its working copy (has_unpublished_versions: true on the returned template), and automations, issues and the API keep sending the CURRENTLY PUBLISHED version until template.publish is called to make the restored design live. The unpublished field on the response is kept for older clients and is always false now; read has_unpublished_versions or message instead. History is FORWARD-ONLY: a restore never rewinds, truncates or reorders the list. It first records the design it is about to replace as its own version, then appends the restored design as the new newest version, so a restore is itself undoable: restore the entry directly above the one you just restored. Restoring the design that is already current is a no-op: nothing is written, and the reply is restored: false with reason "identical" and unpublished: false. Only the newest versions are kept (see retention on template.versions) and consecutive edits by the same author through the same channel (Studio, or one API key) inside the coalesce window collapse into one entry, so a version can age out of history: asking for one that has returns 404 with code template_version_not_found. Returns { restored, restored_from_version, unpublished, message, template }.
template.unpublish
ChatGPTReturn a published email template to draft, taking it out of circulation without deleting it. The body is untouched and published_at is kept as history, and only sendability is retracted, so anything that seeds from this template stops finding it. This is the ONLY way to stop a published template from sending (short of deleting it): editing or restoring it no longer does that on its own. It also drops the published version, so the next template.publish starts from the current (working) content, not the old live one. Unpublishing a template that is already a draft is a no-op, not an error, and template.publish puts it back.
template.update
ChatGPTUpdate an email template. Pass only the fields to change. Providing spec re-renders email-safe HTML server-side; providing html switches the template to raw HTML; providing editor_doc switches it to format "editor" and re-renders. editor_doc is a TipTap/ProseMirror document, {"type":"doc","content":[ ...block nodes ]}. The server renders the email HTML from it and stores that, and refuses a request that also carries html. Every node is {"type":"...","attrs":{...},"content":[...]}. Block nodes that render: paragraph, heading (attrs.level 1-6), bulletList / orderedList / listItem, blockquote, horizontalRule, image (attrs.src, attrs.alt), button (attrs.href, attrs.variant "filled"|"outline", attrs.alignment "left"|"center"|"right", attrs.fullWidth), spacer (attrs.height "sm"|"md"|"lg"|"xl"), table, twoColumns / threeColumns / fourColumns each wrapping columnsColumn children, linkCard, logo, footer, htmlCodeBlock, section. Any block takes attrs.visibility: "all" (default), "email" (default for footer and logo) or "web". In raw HTML: <!--mailtea:visibility email|web-->...<!--/mailtea:visibility-->. Text is {"type":"text","text":"..."} carrying optional marks: bold, italic, underline, strike, code, sub, sup, textStyle, and link (attrs.href). A "subtitle" string on the doc root becomes the inbox preview text. Minimal example: {"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Hello"}]},{"type":"paragraph","content":[{"type":"text","text":"Welcome aboard."}]}]}. These node types render to NOTHING in email and their content is lost: youtube, xPost, threadsPost, codeBlock (use htmlCodeBlock for raw HTML); repeat renders its content once; a showifkey attribute hides the block for contacts whose property is empty. A document that renders to an empty email is refused with 400 editor_doc_unrenderable, and the response names the offending types in node_types. Bounds: 512 KB serialized, 256 KB per string, 40 levels deep. Sending html for a template that is ALREADY format "editor" is refused with 400 editor_template_html_not_accepted, since its html is derived and accepting raw html would orphan the design source; send editor_doc instead. The sidecars are sticky: a patch carrying only editor_doc keeps the stored style_profile / mailtea_theme / global_css, and a patch carrying only a sidecar re-bakes the html from the STORED doc, so the rendered email never drifts from the stored styling. Call template.get first to read the current editor_doc. Editing a PUBLISHED template no longer unpublishes it: the change is saved as the working copy and the template keeps its published status and its published version keeps sending, with has_unpublished_versions: true on the response. That includes from, reply_to and style_profile: they are part of the published version too, so a new sender, reply-to address or page style reaches sends only after the next publish. Call template.publish to make the edit live. Pass base_revision (the revision from your last read) so your edit never overwrites a change someone made since; on a 409 stale_write, re-read with template.get and retry.
template.versions
ChatGPTList an email template's design history, newest first. Metadata only: one version row carries a whole design document, so the list returns version (integer), origin ("edit" | "publish" | "restore"), restored_from_version, format, name, from, reply_to, sender_recorded, sealed, is_current, is_published, created_at, updated_at and author (or null), never the document itself. from and reply_to are the sender the version holds, and a change to only From or Reply-To records a version (or folds into the open one, like any edit). sender_recorded says what a null means: true, the version had no From or Reply-To and restoring it clears them; false, the version was recorded before versions kept the sender, and restoring it leaves the current ones alone. is_current marks the entry that matches the working copy (the saved design you are editing), NOT necessarily what is sending, and not always the newest entry either: a metadata-only update (renaming, retagging) touches the template without recording a version. is_published marks the entry automations and the API are sending now; the two differ while the template has unpublished changes. is_published is false on every entry of a draft, and on every entry of a template published before the field existed until it is published again. The reply also carries retention: { max_versions, coalesce_window_seconds }. Only the newest max_versions per template are kept, and consecutive edits by the same author through the same channel (Studio, or one API key) inside the coalesce window collapse into one entry, so this is a history of saved designs, not a keystroke log. Feed a version number to template.restore_version to put that design, and its From and Reply-To, back.
topic.create
ChatGPTCreate a topic definition. NOTE: topics cannot yet be assigned to individual contacts via the API — this manages topic definitions only.
topic.delete
ChatGPTDelete a topic definition.
topic.list
ChatGPTList topic definitions for a publication.
topic.update
ChatGPTUpdate a topic definition's name, description, default subscription, or visibility.
webhook.create
ChatGPTRegister an outbound webhook endpoint that receives delivery/engagement events. Returns a signing_secret (shown only once) used to verify payloads. Event names and payload shapes: https://docs.mailtea.app/docs/api-reference/webhooks/events
webhook.delete
ChatGPTDelete an outbound webhook.
webhook.get
ChatGPTGet a single outbound webhook by ID.
webhook.list
ChatGPTList outbound webhooks for a publication (signing secrets omitted).
webhook.update
ChatGPTUpdate a webhook's endpoint, subscribed events, or enabled/disabled status. Event names and payload shapes: https://docs.mailtea.app/docs/api-reference/webhooks/events