Sendly vs Mailgun
Mailgun is an email API with deliverability tooling around it. Sendly adds campaigns, workflows and inbound mail, and speaks Mailgun's messages endpoint so mailgun.js keeps working.
Last updated 2026-09-04
The short version
Mailgun is an email API with a deliverability toolkit around it — validation, routing, logs. Teams that chose it usually did so because they wanted the API first and the dashboard second.
Sendly is built the same way round: everything the dashboard does is in a published REST contract, and both official SDKs are generated from it. What it adds is the half most transactional providers leave to a second vendor — marketing campaigns, a visual workflow builder, dynamic segments, and mailboxes that receive real mail on your domain.
What it does not add is a validation product or a separate inbound routing DSL. If those are load-bearing for you, check that against the list below before you plan anything.
mailgun.js keeps working, with a URL override
Sendly speaks Mailgun's transactional-send dialect on a dedicated endpoint at /api/compat/mailgun. Keep the Mailgun SDK you already run, point it at Sendly, and swap the key. There is no rewrite and no second client library.
The endpoint is a translator, not a side door. It parses the vendor request shape, maps it onto Sendly's native send, and runs it through the same pipeline your native API calls use — so verified-domain checks, sending limits, suppression and billing all still apply.
Where a dialect can express something Sendly does not support in this phase, the endpoint returns a clean error in that vendor's own error shape rather than dropping the field silently. Your existing error handling fires exactly as it would have.
Sendly implements Mailgun's POST /v3/<domain>/messages. Authentication is Mailgun's own HTTP Basic style, where the username is the literal string api and the password is your Sendly secret key, so the SDK's client options are the only thing that changes.
import formData from "form-data";
import Mailgun from "mailgun.js";
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: "api",
key: process.env.SENDLY_API_KEY,
url: "https://api.sendly.now/api/compat/mailgun",
});
// unchanged from here down
await mg.messages.create("yourdomain.com", {
from: "you@yourdomain.com",
to: "customer@example.com",
subject: "Your receipt",
html: "<p>Thanks for your order.</p>",
});Two gotchas worth reading before you switch a production sender
- The domain in the URL path must match the domain of your from address. A mismatch is a 400 in Mailgun's error shape, not a best-effort send.
- Mailgun options past o:tag — scheduled delivery via o:deliverytime, tracking toggles, stored templates — are not applied. Unlike the Resend and SendGrid dialects, which reject what they cannot map, the Mailgun dialect honours the fields in the supported list and ignores the rest. If your sends lean on those options, that is a behaviour change to test for, not a footnote.
Mailgun publishes no official Python SDK, so Python senders either call the compat endpoint over plain multipart HTTP or use the Sendly Python SDK directly — for a Python codebase, moving to the native SDK is usually less work than preserving the vendor shape.
What Sendly does
Sendly is one workspace for transactional email, marketing campaigns and automation, against one contact database. Everything below is in the product today, not on a roadmap.
| Area | What you get |
|---|---|
| Transactional send | REST API with idempotent sends, batch send, scheduling and cancellation, template variables, attachments, and a delivery record you can read back. |
| Campaigns | One-off broadcasts to a list or a segment, with scheduling, pause and resume, and per-campaign stats. |
| Workflows | A visual builder for automated sequences, with branching on contact data and on tracked events. Runs server-side, with an execution log per contact. |
| Contacts and segments | Custom fields, CSV import, and dynamic segments that re-evaluate from contact data and behaviour. Built for millions of rows, with cursor pagination throughout. |
| Deliverability | Domain verification with guided DNS setup that publishes the records for you and then watches them for drift. SPF, DKIM and DMARC alignment, suppression list, bounce and complaint handling. |
| Inbound | Real mailboxes on your own domain, reachable over IMAP and SMTP, with the messages also projected into the API. |
| Events and webhooks | An event stream you can query and subscribe to, signed webhook deliveries with retries, and tracked custom events that can trigger a workflow. |
| AI agents | A remote MCP server, so an agent can work in your account under permissions you tick yourself. Irreversible ones are never granted by default. |
| SDKs | Official open-source clients for JavaScript and Python, both at 1.0.0, generated from the published OpenAPI contract. |
| Self-hosting | Documented Docker deployment, if you would rather run it yourself. |
What Sendly costs
There is no monthly platform fee and there is no plan to outgrow. A free tier, metered usage that gets cheaper as volume rises, and a conversation for anything past that.
| Plan | Price | What it is |
|---|---|---|
| Free | $0 / month · forever | Everything you need to get started. No credit card required. 5,000 emails / month · Transactional, campaigns & workflows · Full REST API, SDKs & webhooks · Powered-by-Sendly badge on sent email |
| Pay as you go | $1.00 / 1,000 emails | Volume pricing that drops as you send more. No monthly fee and no plan to outgrow. $1.00 per 1,000 emails up to 50,000 · $0.70 per 1,000 above 50,000 · $0.50 per 1,000 above 250,000 · Badge removed from your email · Set your own limit per category · Billed in USD, EUR or GBP |
| Custom | Let's talk tailored to your volume | High-volume or regulated sending that needs limits set by a human. Everything in Pay as you go · Custom sending limits · Higher daily throughput · Direct support from the team |
The free tier is a real tier, not a trial: it does not expire, and nothing about it is time-limited. Its allowance is shared across transactional sends, campaigns, workflows and inbound mail, because Sendly meters emails rather than products.
What to check on Mailgun's side
We do not publish Mailgun's prices or limits here. They change without telling us, and a stale number on this page would be the one thing on it you could catch us being wrong about. These are the questions worth answering from their own documentation before you move anything.
- What does the bill look like at your actual monthly volume, and at ten times it? Flat plans and metered curves cross over somewhere; find where.
- Are transactional sends and marketing sends billed and rate-limited from the same allowance, or two?
- Is there a contact-count charge on top of the send charge, and does an unsubscribed contact still count?
- What happens on the day you exceed the plan — a hard stop, an overage rate, or an upgrade prompt?
- Can you export your contacts, templates and suppression list in full, without asking support?
- Is there an API for everything the dashboard can do, and is the contract published?
Questions people ask
- Does Sendly do email address validation?
- Not as a standalone product. Sendly maintains a suppression list, handles bounces and complaints, and will stop you mailing an address that asked you to stop — but there is no pre-send verification API to check whether an address exists. If you buy Mailgun for validation specifically, that part does not have an equivalent here.
- Can I receive mail on Sendly the way Mailgun routes handle inbound?
- Sendly gives you real mailboxes on your own domain, reachable over IMAP and SMTP, and projects the messages into the API so your code can read them. It is a mailbox model rather than a routing-rule model: there is no expression language that forwards or posts a matching message onward. Read the receiving-emails guide before assuming a route maps across.
- Will my o:tag values survive?
- They map onto Sendly tags, sanitized. It is a partial mapping rather than an identity, so once you have moved, treat the tag values Sendly reports as the source of truth rather than assuming byte equality with what you sent.
- What does a bad key look like?
- A 401 with Mailgun's bare { message } error body, so mailgun.js raises the error your existing handling already catches and error.status and error.details read the way they do today.
Something on this page out of date, or missing the case you are in? Write to support@sendly.now — we answer our own email. The full technical detail lives in the documentation.