Connect Claude, ChatGPT, Cursor, or any MCP-compatible client to your Mailsoftly account and work with contacts, lists and campaigns in plain language.
Model Context Protocol (MCP) is an open standard that lets AI assistants connect to external tools and data sources. With Mailsoftly's MCP server, an assistant can read your contacts and lists, prepare campaign drafts, and (only if you allow it) send them.
Pick one. Most people want the hosted server; the local server exists for developers who prefer to run the process themselves.
Paste one URL into your AI client and sign in with your Mailsoftly account. No install, no API key to copy, no key sitting in a config file. Works with Claude and ChatGPT.
Run the open source Node.js server on your own machine and point Claude Desktop, Cursor or VS Code at it. Authenticates with a Mailsoftly API key.
| Hosted server | Local server (stdio) | |
|---|---|---|
| Setup | Paste a URL, approve the permissions | Clone, build, edit a JSON config |
| Sign-in | OAuth, no key to copy | Mailsoftly API key |
| Runs on | Mailsoftly servers | Your machine |
| Tools | 139 curated tools | One tool per live API endpoint |
| Turn it off | Settings > Connected apps | Delete the key or the config entry |
The hosted server lives at https://app.mailsoftly.com/mcp. It speaks Streamable HTTP and is authorized with OAuth, so the only thing you ever give your AI client is that URL. Your client registers itself, sends you to Mailsoftly to sign in, and receives a token scoped to exactly the permissions you approved.
In Claude, go to Settings, then Connectors, then Add custom connector.
Give it the name Mailsoftly and the URL https://app.mailsoftly.com/mcp, then confirm.
Claude opens Mailsoftly in your browser. Sign in, read the list of permissions the app is asking for, and approve. You land back in Claude with the connector ready.
In ChatGPT, open the settings area where connectors and apps are managed and choose to add one by URL.
https://app.mailsoftly.com/mcp. ChatGPT walks the same sign-in and consent flow as Claude.
Any other MCP client that supports remote servers with OAuth works the same way: give it the server URL and let it discover the rest.
Connecting a third-party app is an admin action. If you are not an admin on your company, the consent screen tells you so and asks you to have an admin connect the app instead.
You do not have to do any of this by hand, but here is what happens behind the URL:
/.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server to learn where to register and where to authorize.POST /oauth/register. Either way it is a public client: no shared secret is created.An app has to ask for what it needs, and you see the request in plain language before anything is granted. Nothing is granted by default.
| Permission | What the app can do with it |
|---|---|
contacts:read | Read your contacts, lists and tags |
contacts:write | Create and update contacts, lists and tags |
campaigns:read | Read your campaigns and their status |
campaigns:draft | Create and edit campaign drafts. Drafts are never delivered on their own |
campaigns:send | Send or schedule campaigns to your contacts. Separate opt-in, see below |
suppressions:write | Add addresses to your suppression list so they stop receiving mail |
account:read | Read your account and company profile |
account:write | Invite teammates and change their roles |
Sending mail cannot be undone, so approving an app never grants it on its own. When an app asks for campaigns:send, the consent screen shows a separate checkbox that starts unchecked. Leave it off and everything else still works: the app can research, draft and prepare, and you press send yourself.
The hosted server publishes 139 curated tools rather than every API operation, so the assistant picks the right one instead of guessing between near-identical endpoints. Reading tools cover contacts and their activity, lists, tags, segments, suppressions, imports, forms, templates, automations, campaigns with their reports (links, recipients, audience), files, images, content blocks, the brand kit, domain health and the team; writing tools add, import and update contacts, build lists, draft and edit campaigns, upload assets, invite teammates, and, only with the send permission, deliver mail or send a test.
| Tool | What it does | Permission |
|---|---|---|
list_contacts | List contacts | contacts:read |
get_contact | Get a single contact | contacts:read |
create_contact | Create a new contact | contacts:write |
update_contact | Update an existing contact | contacts:write |
create_or_update_contact | Create or update a contact | contacts:write |
search_contacts | Search contacts | contacts:read |
list_contact_lists | List all contact lists | contacts:read |
list_contacts_in_list | List contacts in a contact list | contacts:read |
create_contact_list | Create a new contact list | contacts:write |
add_contact_to_contact_list | Add a single contact to a list | contacts:write |
add_contacts_to_contact_list | Add several contacts to a list | contacts:write |
list_tags | List all tags | contacts:read |
assign_tags_to_contact | Assign tags to a contact | contacts:write |
get_custom_fields | List all custom fields | contacts:read |
draft_campaign | Create email drafts | campaigns:draft |
get_campaign_status | Check email draft status | campaigns:read |
send_campaign | Send an email draft (cannot be undone) | campaigns:send |
unsubscribe_contact | Globally unsubscribe an email (cannot be undone) | suppressions:write |
reinstate_contact | Remove an address from the suppression list (cannot be undone) | suppressions:write |
unsubscribe_sms | Opt a phone number out of SMS (cannot be undone) | suppressions:write |
reinstate_sms | Remove a phone number from the SMS opt-out list (cannot be undone) | suppressions:write |
list_email_templates | List email templates | campaigns:read |
get_email_template | Get an email template | campaigns:read |
list_senders | List senders | account:read |
list_email_sender_addresses | List sender addresses | account:read |
list_automations | List automations | campaigns:read |
get_automation | Get an automation | campaigns:read |
enroll_contact_in_automation | Enrol a contact in an automation (cannot be undone) | campaigns:send |
list_forms | List forms | contacts:read |
get_form | Get a form | contacts:read |
get_form_responses | Get form submissions | contacts:read |
list_landing_pages | List landing pages | campaigns:read |
get_landing_page | Get a landing page | campaigns:read |
list_sms_campaigns | List SMS campaigns | campaigns:read |
get_sms_campaign | Get an SMS campaign | campaigns:read |
list_segments | List segments | contacts:read |
list_integrations | List connected integrations | account:read |
list_webhook_endpoints | List webhook endpoints | account:read |
list_signatures | List email signatures | account:read |
list_suppressions | List suppressed addresses | contacts:read |
get_account | Get account and sending state | account:read |
list_team_members | List team members | account:read |
update_campaign | Update a campaign draft (cannot be undone) | campaigns:draft |
list_campaigns | List campaigns | campaigns:read |
get_campaign_stats | Get campaign performance | campaigns:read |
create_automation | Create an automation | campaigns:draft |
update_automation_status | Activate or deactivate an automation (cannot be undone) | campaigns:send |
schedule_campaign | Schedule a campaign (cannot be undone) | campaigns:send |
delete_contact | Delete a contact (cannot be undone) | contacts:write |
remove_tags_from_contact | Remove tags from a contact (cannot be undone) | contacts:write |
remove_contact_from_list | Remove a contact from a list (cannot be undone) | contacts:write |
rename_contact_list | Rename a contact list | contacts:write |
delete_contact_list | Delete a contact list (cannot be undone) | contacts:write |
create_tag | Create a tag | contacts:write |
update_tag | Rename or recolor a tag | contacts:write |
delete_tag | Delete a tag (cannot be undone) | contacts:write |
create_email_template | Create an email template | campaigns:draft |
update_email_template | Update an email template (cannot be undone) | campaigns:draft |
delete_email_template | Delete an email template (cannot be undone) | campaigns:draft |
trash_campaign | Move a campaign to the trash | campaigns:draft |
delete_campaign | Delete an unsent campaign permanently (cannot be undone) | campaigns:draft |
restore_campaign | Restore a campaign from the trash | campaigns:draft |
delete_automation | Delete an automation (cannot be undone) | campaigns:draft |
create_form | Create a form | contacts:write |
update_form | Update a form (cannot be undone) | contacts:write |
delete_form | Delete a form (cannot be undone) | contacts:write |
create_landing_page | Create a landing page | campaigns:draft |
update_landing_page | Update a landing page (cannot be undone) | campaigns:draft |
publish_landing_page | Publish or unpublish a landing page (cannot be undone) | campaigns:draft |
trash_landing_page | Move a landing page to the trash | campaigns:draft |
create_webhook_endpoint | Create a webhook endpoint | contacts:write |
delete_webhook_endpoint | Delete a webhook endpoint (cannot be undone) | contacts:write |
update_automation | Update an automation (cannot be undone) | campaigns:draft |
start_ab_test | Start an A/B subject test (cannot be undone) | campaigns:send |
list_domains | List sending domains | account:read |
add_domain | Add a sending domain | campaigns:draft |
verify_domain | Verify a sending domain | campaigns:draft |
remove_domain | Archive a sending domain (cannot be undone) | campaigns:draft |
create_sender | Create a sender address | campaigns:draft |
delete_sender | Remove a sender address (cannot be undone) | campaigns:draft |
list_reply_to_addresses | List reply-to addresses | account:read |
create_reply_to_address | Add a reply-to address | campaigns:draft |
create_segment | Create a segment | contacts:write |
update_segment | Update a segment | contacts:write |
delete_segment | Delete a segment (cannot be undone) | contacts:write |
refresh_segment | Recalculate a segment | contacts:write |
create_custom_field | Define a custom contact field | contacts:write |
list_email_types | List email types | contacts:read |
create_email_type | Create an email type | contacts:write |
create_signature | Create an email signature | campaigns:draft |
update_signature | Update an email signature (cannot be undone) | campaigns:draft |
delete_signature | Delete an email signature (cannot be undone) | campaigns:draft |
unschedule_campaign | Cancel a scheduled send | campaigns:draft |
create_sms_campaign | Create an SMS campaign | campaigns:draft |
update_sms_campaign | Update an SMS campaign (cannot be undone) | campaigns:draft |
send_sms_campaign | Submit an SMS campaign for sending (cannot be undone) | campaigns:send |
get_campaign_report | Get a full campaign report | campaigns:read |
get_campaign_links | Get per link clicks for a campaign | campaigns:read |
get_campaign_recipients | List the recipients of a campaign | campaigns:read |
get_contact_activity | Get one contact's activity timeline | contacts:read |
add_contact_note | Write a note on a contact | contacts:write |
preview_campaign_audience | Preview who a campaign would go to | campaigns:read |
import_contacts | Import contacts from a CSV | contacts:write |
get_import | Get import progress | contacts:read |
list_imports | List contact imports | contacts:read |
get_contact_subscriptions | Get a contact's subscriptions | contacts:read |
update_contact_subscriptions | Change a contact's subscriptions (cannot be undone) | suppressions:write |
get_compliance_summary | Get the compliance summary for a region | contacts:read |
get_consent_evidence | List consent evidence | contacts:read |
get_opt_outs | List opt-out records | contacts:read |
record_consent | Record a consent | contacts:write |
get_iys_summary | Get the registry filing status | contacts:read |
get_campaign | Get one campaign in full | campaigns:read |
send_test_email | Send a test copy of a campaign (cannot be undone) | campaigns:send |
duplicate_campaign | Copy a campaign into a new draft | campaigns:draft |
update_campaign_sharing | Change how a campaign is shared on the web (cannot be undone) | campaigns:draft |
add_campaign_attachment | Attach a file to a draft campaign | campaigns:draft |
remove_campaign_attachment | Remove a file from a draft campaign (cannot be undone) | campaigns:draft |
list_files | List hosted files | campaigns:read |
upload_file | Upload a file to the gallery | campaigns:draft |
delete_file | Remove a file from the gallery (cannot be undone) | campaigns:draft |
list_images | List the image library | campaigns:read |
upload_image | Upload an image | campaigns:draft |
list_custom_blocks | List saved content blocks | campaigns:read |
get_custom_block | Get one content block | campaigns:read |
create_custom_block | Save a content block | campaigns:draft |
update_custom_block | Update a content block (cannot be undone) | campaigns:draft |
delete_custom_block | Delete a content block (cannot be undone) | campaigns:draft |
get_brand_kit | Get the brand kit | account:read |
update_brand_kit | Update the brand kit (cannot be undone) | campaigns:draft |
invite_team_member | Invite a teammate (cannot be undone) | account:write |
list_pending_invitations | List pending invitations | account:read |
revoke_invitation | Revoke a pending invitation (cannot be undone) | account:write |
update_team_member_role | Change a teammate's role | account:write |
list_api_keys | List the account's API keys | account:read |
list_connected_apps | List connected apps | account:read |
list_audit_events | List the account's audit trail | account:read |
list_domain_health_checks | List saved domain health checks | account:read |
run_domain_health_check | Run a domain health check | campaigns:draft |
A draft's body comes in two formats. body_format: "content" (the default) takes an HTML fragment and places it inside Mailsoftly's responsive layout, so it renders well on phones without any email-CSS work. body_format: "html" takes a complete HTML email you built yourself and stores and sends it exactly as written. Passing template_id instead starts the draft from a saved template, so an assistant can reuse a design your team already approved. update_campaign edits the draft in place afterwards.
An assistant can build an automation the user describes, as a draft: a trigger (a contact is created or updated, a tag is added, a contact joins a list, a form is submitted, a contact unsubscribes) and ordered steps (send an email from a saved template, add or remove tags, add to or remove from lists, wait, post a webhook, stop). Activating it is a separate call that needs the send permission and a verified account, and a contact can be enrolled in an active automation on request. Plan caps apply exactly as in the builder: a free, unverified account may keep a small number of automations, forms and pages, and the response says so with an upgrade or verify path instead of a bare error.
Sending is gated on purpose: a new account needs a sending channel of its own, and every account's first campaigns pass a short content review. Instead of a bare error, send_campaign, get_account, list_senders and get_campaign_status return a gate object that names the level (account or campaign), the reason, a message the assistant can relay verbatim, and unlock_paths with links into your settings. Account-level gates unlock by connecting a Google Workspace or Microsoft 365 account (verified organizations are trusted automatically), connecting a personal Gmail or Outlook mailbox, or authenticating your own sending domain. Campaign-level gates, such as the content review, have no unlock path: the campaign goes out when it is approved, and no account change speeds that up.
The local server is a small open source Node.js process that runs on your machine and talks to the Mailsoftly API with your API key. It builds its tools from the published OpenAPI spec, so it exposes one tool per live endpoint rather than the curated 139. Use it when your client cannot connect to a remote MCP server, or when you want to run everything yourself.
The server lives in the Mailsoftly repository under mcp-server/.
# Navigate to the MCP server directory cd mcp-server # Install dependencies npm install # Build the server npm run build
Create a .env file in the mcp-server/ directory with your Mailsoftly API key.
API_KEY_API_KEY=your-mailsoftly-api-key-here API_BASE_URL=https://app.mailsoftly.com
Add the following to your Claude Desktop configuration file.
{
"mcpServers": {
"mailsoftly": {
"command": "node",
"args": ["/path/to/mcp-server/build/index.js"],
"env": {
"API_KEY_API_KEY": "your-mailsoftly-api-key",
"API_BASE_URL": "https://app.mailsoftly.com"
}
}
}
}
Add the same configuration to your Cursor or VS Code MCP settings.
{
"mcpServers": {
"mailsoftly": {
"command": "node",
"args": ["/path/to/mcp-server/build/index.js"],
"env": {
"API_KEY_API_KEY": "your-mailsoftly-api-key",
"API_BASE_URL": "https://app.mailsoftly.com"
}
}
}
}
Restart your AI tool and ask it to work with your account. Try "List my contact lists" or "Create a new contact".
API keys are managed in Settings > API Keys. A new key expires one year after it is created, is stored as a hash so the key itself is visible only once at creation, and records when it was last used, which makes an unused key easy to spot and revoke. Keys created before this change keep working exactly as before, with no expiry.
Both servers sit on top of the same REST API: 151 live endpoints covering contacts, lists, tags, segments, custom fields, imports and activity, campaigns with their reports, templates, automations, forms, landing pages, files and images, sending identity, team and account. If you would rather call it directly, or wire it into a platform that speaks OpenAPI instead of MCP, everything is documented at API Documentation with a machine-readable spec at app.mailsoftly.com/developers/openapi.json.
Use the OpenAPI spec in a custom GPT's actions to work with campaigns and contacts. Spec: /developers/openapi.json
Import the OpenAPI spec as a Vertex AI Extension to manage contacts and campaigns from Gemini.
The same spec drives tool use and function calling in any framework that reads OpenAPI.
For direct API calls the key goes in the Authorization header without a Bearer prefix. OAuth access tokens from the hosted server are sent as Authorization: Bearer <token>.
Hosted server. No key is ever pasted into a client. Access is granted by OAuth after a Mailsoftly admin approves it on a consent screen that lists the permissions in plain language, sending is a separate opt-in, access tokens are short-lived, refresh tokens rotate, and every connection can be revoked from Settings > Connected apps. Each tool call is checked against the permissions that were actually granted, and the connected app is recorded on the contact activity it creates, so you can see later which app did what.
Local server. The process runs on your machine and never sends your key anywhere except app.mailsoftly.com. The key sits in your .env file or MCP config, is stored hashed on our side, and expires a year after you create it.
Either way you are talking only to Mailsoftly: there is no third-party relay in between, and the data an assistant can reach is the data of the one account you connected. See our privacy policy for how account data is handled.
Add https://app.mailsoftly.com/mcp in your AI client and approve the permissions. That is the whole setup.