Mailsac vs testmail.app
Mailsac vs testmail.app
testmail.app alternative for automated email testing
testmail.app and Mailsac both receive the emails your application sends and hand them to your tests over an API. testmail.app gives you a namespace under inbox.testmail.app, a JSON and a GraphQL API, and a server-side wait it calls a live query. Mailsac gives you public @mailsac.com inboxes, your own test domain from $18 a month, a REST API, and webhooks and WebSockets that push each email to your test. Here is how they compare, where testmail.app is the better fit, and how to move existing tests.
Reviewed September 2026 against both vendors’ public pricing and documentation.
The free Mailsac plan includes an API key, one private address and 1,500 Ops a month, with no expiry, and any name@mailsac.com inbox can receive mail with no account at all. Jump to the full comparison or see how to move from testmail.app.
At a glance
Free plan
Mailsac API key, 1 private address, 1,500 Ops a month; public inboxes need no account
testmail.app 100 emails a month, 1 day retention, one random namespace
Entry paid plan
Mailsac Indie, $18 a month or $169 a year: 25,000 Ops, one custom domain
testmail.app Essential, $9 a month billed yearly or $12 monthly: 10,000 emails
Your own test domain
Mailsac Every paid plan; a zero-setup msdc.co subdomain or your own domain
testmail.app Listed on the Unlimited plan, $269 a month billed yearly
Waiting for an email
Mailsac Poll, or push to a webhook, WebSocket or Slack on every plan
testmail.app Live query: the API holds your request until a match; no webhooks
Query API
Mailsac REST and JSON; parsed links on every message
testmail.app JSON API, plus GraphQL with filters and sorts
SAML single sign-on
Mailsac Business, $89 a month or $840 a year
testmail.app Enterprise, $89 a month billed yearly or $129 monthly; SAML or OIDC
Which should you choose?
Both are receive-only services built for end-to-end tests: your app sends real email, the service receives it, and your test reads it back. On both, every address already exists, so there is nothing to create before a test runs. They differ in where the mail lands (a testmail.app namespace or a domain you control), how you wait for it (a held-open request or a push), what usage is counted (emails or Ops), and how long mail is kept.
Choose Mailsac if
- You want test mail on a domain you control at a small-team price. Indie ($18 a month) includes one custom domain or a zero-setup
yourteam.msdc.cosubdomain; Business includes 5. testmail.app addresses arenamespace.tag@inbox.testmail.app, and custom domains appear only on its Unlimited plan at $269 a month billed yearly. - You would rather be pushed than hold a request open. Webhooks, Slack forwarding and WebSockets are on every Mailsac plan, including the free plan’s private address. testmail.app’s live query keeps an HTTP request open and redirects it to itself every minute; its documentation has no webhooks or WebSockets.
- You need to capture a staging app’s outgoing mail over SMTP without changing who it emails. Email Capture is on every plan. testmail.app only receives mail delivered to its own domain.
- You want a throwaway inbox with no account. Any
@mailsac.comaddress can be read on the website. testmail.app has no public inboxes: everything sits in your namespace behind an API key, and its pricing page lists the visual viewer from Pro up. - You want to delete test mail when a test ends, or keep it until you say otherwise. Mailsac has delete endpoints per message, inbox and domain, and keeps private mail up to a stored-message count with starring to keep it longer. testmail.app has no delete in its API or console and expires mail after 1, 3 or 30 days by plan.
- Procurement wants published prices all the way up: Business+ at $1,900 a year with invoice or purchase order, and Enterprise from $7,500 a year with a published ladder, ACH or wire, resellers SHI and SoftwareOne, and help with SIG and CyberGRX questionnaires. testmail.app publishes plans up to $269 a month and quotes above 10 million emails.
Choose testmail.app if
- You want the lower entry price and a bigger monthly count. Essential is $9 a month billed yearly ($12 monthly) for 10,000 emails and unlimited users. Mailsac’s Indie is $18 a month for 25,000 Ops, about 7,500 test emails by Mailsac’s own estimate, and one user. The units differ, but at the low end testmail.app includes more mail for less money.
- Namespace and tag addressing suits how you organise tests. A tag like
run42.signupmakes a new inbox on the fly,tag_prefixfetches a whole test run at once, and each API key can be limited to certain namespaces on every plan. - You want GraphQL. The inbox query takes include or exclude filters on from, to, subject, text and HTML (exact or wildcard), custom sorts and a choice of returned fields. Mailsac’s REST API returns fixed JSON and filters on to, from and subject; anything else you match in your test.
- You want a spam report with every email. testmail.app runs SpamAssassin on each message and returns the score and the full rule report, plus SPF and DKIM fields. Mailsac’s
spamfield is an experimental score from 0 to 1 with no report. - Your usage is spiky. Paid testmail.app plans allow unlimited temporary surges past the monthly allowance, with an upgrade required only if you regularly exceed it. Mailsac emails you at 80% of your Ops and pauses the API at 125% until the month resets or you upgrade.
- You want unlimited team members on every plan, a 14-day trial of paid features, a 99.99% uptime SLA and SAML or OIDC single sign-on on its Enterprise plan ($89 a month billed yearly), or SCIM on Unlimited. Mailsac’s free plan has no trial period, and users are 1, 5, 10 and 25 by plan.
Mailsac vs testmail.app: features and prices
Prices are list prices in US dollars, before tax, as each vendor shows them. Mailsac counts Ops, which include API calls; testmail.app counts emails received and rate-limits API requests instead of counting them. The units are not directly comparable, so compare by the job you need done.
| Mailsac | testmail.app | |
|---|---|---|
| Plans and prices | ||
| Free | No expiry. API key, 1 private address, 50 stored messages, 1,500 Ops a month, WebSocket and webhook forwarding on the private address. Any @mailsac.com inbox receives mail with no account |
Free forever after a 14-day trial of paid features. 100 emails a month, 1 day retention, one random namespace, unlimited users. API requests capped at 1,000 an hour, 10,000 a day and 100,000 a month |
| Entry paid plan | Indie: $18 a month, or $169 a year. 25,000 Ops, 1 user, 1 custom domain, 50 private addresses, 1,000 stored messages | Essential: $9 a month billed yearly, or $12 monthly. 10,000 emails a month, 1 to 3 days retention, one random namespace, unlimited users |
| Mid plans | Business: $89 a month, or $840 a year. 500,000 Ops, 5 users, 5 domains, SAML single sign-on, multiple named API keys. Business+: $199 a month, or $1,900 a year. 2 million Ops, 10 users, 10 domains | Pro: $29 a month billed yearly, or $39 monthly. 50,000 emails, 1 to 30 days retention, custom namespaces, visual viewer, priority support. Enterprise: $89 a month billed yearly, or $129 monthly. 1 million emails, custom retention, SAML 2.0 or OIDC single sign-on, invoicing, 99.99% uptime SLA |
| Top plan | Enterprise: from $7,500 a year (2 million Ops a month), or $799 a month by card, with a published ladder to 20 million Ops. 25 users, 12 or more domains | Unlimited: $269 a month billed yearly, or $299 monthly. 10 million emails, unlimited teams, unlimited custom domains, SCIM, custom contracts. Above 10 million emails by quote |
| Yearly billing | Yearly prices published; about 20% less than paying monthly | Yearly and monthly prices published; the headline price is the yearly rate |
| What usage is counted | Ops. One Op is an API call, a message received at a private address or domain, or a webhook, Slack or WebSocket push. Mail to public @mailsac.com inboxes is not counted, but reading it over the API is. A polled test typically uses 3 to 6 Ops; a pushed email uses 1 |
Emails received a month. API requests are not counted, only rate-limited: sustained bursts over 10 requests a second from one IP, or over 5 a second per key for an hour on paid plans, are blocked temporarily |
| Going over | Email at 80% and at 100%; service continues to 125%, then the API pauses until the 1st of the next month unless you upgrade | Unlimited temporary surges on paid plans at no charge; an upgrade is required if you regularly exceed the allowance. A fair-use policy covers unusually heavy use such as huge attachments |
| Addresses and domains | ||
| Address format | Anything @mailsac.com (public), anything on your custom domain or msdc.co subdomain (private), or a reserved private address. Nothing to create |
{namespace}.{tag}@inbox.testmail.app. The namespace is assigned (random by default, custom on Pro and up); the tag is anything you choose. Nothing to create |
| Your own domain | Every paid plan: 1 on Indie, 5 on Business, 10 on Business+, 12 or more on Enterprise; extra domains $7 a month. A yourteam.msdc.co subdomain is ready immediately; your own domain needs a TXT record, then MX records |
“Unlimited custom domains” on the Unlimited plan only ($269 a month billed yearly). Not described in the API documentation |
| Public inboxes | Yes. Any @mailsac.com inbox, readable by anyone at mailsac.com/inbox/<name>; mail kept up to 4 days, most recent few messages only |
None. Every inbox belongs to a namespace and is read with an authorised API key |
| Grouping addresses | By domain: GET /api/domains/{domain}/messages lists a whole domain, and a catch-all address forwards all of it. Account-wide GET /api/inbox, /api/inbox-search and /api/inbox-filter |
By namespace and tag: query a namespace, one tag, or a tag_prefix such as a test-run ID. Multiple namespaces on Pro and up, each with its own API keys |
| Reading mail in a browser | Every plan, on the website; public inboxes without an account | Visual viewer listed on Pro and up. The JSON API also works in a browser with &pretty=true |
| API and test automation | ||
| APIs and auth | REST at https://mailsac.com/api with a Mailsac-Key header (or _mailsacKey query parameter). OpenAPI 3 spec published |
JSON API: GET https://api.testmail.app/api/json?apikey=&namespace= (GET only). GraphQL: POST https://api.testmail.app/api/graphql with Authorization: Bearer; self-documenting schema and playground |
| Official client libraries | @mailsac/api (JavaScript/TypeScript) and the @mailsac/cypress plugin. Any other language over REST |
@testmail.app/graphql-request on npm (a GraphQL client with retries). HTTP examples in JavaScript, PHP, Ruby, Python, Go, bash, C# and Java; Cypress, Selenium and TestCafe examples |
| Waiting for new mail | Poll GET /api/addresses/{email}/messages (newest first) against a deadline, or have a private address or domain push each message to a webhook, WebSocket or Slack. The Cypress plugin waits for you |
Live query: add livequery=true and the API waits until a matching email exists, returning an HTTP 307 redirect to itself every minute. Your client must follow redirects and your test suite needs its own timeout |
| Filtering | By address or domain, limit and until (a date). Account-wide search on to, from and subject. Match subject and received time in your test |
JSON API: tag, tag_prefix, timestamp_from, timestamp_to, limit (up to 100), offset. GraphQL adds include or exclude filters on from, to, cc, subject, text, html and sender IP (exact or wildcard), and custom sorts |
| Codes and links | Every message carries a links array of URLs found in its text and HTML. Plain text at /api/text/; codes are matched in your test, or with extractCode in the Cypress plugin |
The email object has text and html; you match links and codes with a regular expression, as the OTP examples in its docs do. No parsed-links field in the schema |
| Attachments | Listed at .../messages/{messageId}/attachments and downloaded by identifier. Maximum message size 2.5 MB |
attachments[] with a downloadUrl per file. Under 5 MB per message recommended; over 10 MB is slow; over 20 MB is rejected |
| Spam and authentication checks | Experimental spam score from 0 to 1 on each message; no report |
SpamAssassin spam_score and full spam_report on request, plus SPF and DKIM fields |
| Deleting mail | DELETE a message or an inbox; POST /api/domains/{domain}/delete-all-domain-mail for a domain |
Not offered in the API or console; the docs call per-test deletion an anti-pattern. Mail expires after the retention period |
| Retention and limits | ||
| How long mail is kept | By count: private mail up to 50 messages on Free, 1,000 on Indie, 5,000 on Business, 10,000 on Business+, oldest recycled first; starred messages are kept. Public mail up to 4 days. More storage $10 a month per 5,000 | By time: 1 day on Free, 1 to 3 days on Essential, 1 to 30 days on Pro, custom on Enterprise and Unlimited. Retention can be adjusted on request |
| Rate limits | No per-second API limit published. Public addresses have a lower inbound throttling threshold than paid domains; Business and up add an IP allowlist for your senders | Global: sustained bursts over 10 requests a second per IP. Per key on paid plans: over 5 a second sustained for an hour, then the key is blocked for up to an hour. Free plan: 1,000 an hour, 10,000 a day, 100,000 a month. Higher limits on request |
| Load testing | Business and up: a temporary domain active for 8 hours, counted rather than stored. Burst throughput on request on Enterprise. See email load testing | No separate feature. Temporary surges are allowed on paid plans; a fair-use policy applies |
| Push, capture and sending | ||
| Webhooks, WebSockets and Slack | Every plan, per private address; for a whole custom domain via a catch-all, with domain-wide WebSockets on Business and up. Endpoint wss://sock.mailsac.com/incoming-messages |
Not documented. Live queries are the way to wait for mail |
| SMTP capture for staging | Email Capture on every plan: point a staging app’s SMTP at capture.mailsac.com:5587 (STARTTLS; your username and API key). Mail to any recipient lands in that recipient’s Mailsac inbox. Public unless private capture is on or the recipient is on your custom domain |
Not offered. Mail has to be delivered to inbox.testmail.app |
| Sending mail | Receive only. New mail can be forwarded to a webhook, WebSocket, Slack or another Mailsac address | Receive only |
| Team, security and buying | ||
| Users and API keys | 1 user on Free and Indie, 5 on Business, 10 on Business+, 25 or more on Enterprise. Multiple named API keys on Business and up | Unlimited users on every plan. Multiple API keys, each limited to chosen namespaces, with usage per key. Multiple teams on the Enterprise plan; unlimited teams on Unlimited |
| Single sign-on | SAML on Business and up (Okta, Azure AD, Google) | SAML 2.0 or OIDC on Enterprise and Unlimited. SCIM provisioning on Unlimited |
| Hosting and certifications | Runs on AWS in the United States. No certification published. Enterprise includes help with SIG and CyberGRX questionnaires and information assurance documentation | Company in Toronto, Canada; runs its own mail servers; APIs across multiple data centres (providers not named). TLS enforced, encryption at rest, passwordless logins, public status page, GDPR data processing agreement. No certification stated; “enterprise compliance support” on Unlimited |
| Support | Forum on Free; email from Indie; priority email on Business+; 2 hours of phone support on Enterprise | Email and live chat from the engineers who build it; priority support on Pro and up; typical response within a few hours |
| Billing | Card. Invoice or purchase order on annual Business+ and Enterprise; ACH, wire and resellers SHI and SoftwareOne on Enterprise | Card. Enterprise invoicing on Enterprise and up; custom contracts on Unlimited; free or discounted plans for open source, non-profits and education on request |
Reviewed September 29, 2026. testmail.app’s plans count emails received a month; Mailsac’s count Ops a month, including API calls and pushes. Check each vendor’s current pricing before you buy.
Sources: testmail.app pricing, documentation, homepage and about page. Mailsac pricing, API reference, message storage, custom domains and Email Capture.
Moving from testmail.app
A testmail.app test sends to a fresh tag in your namespace, runs a live query filtered by tag and timestamp_from, and reads text or html from the returned email. A Mailsac test has the same shape: send to a fresh address, wait, read. What changes is that the address lives on your domain, the wait is a polling loop or a push, and the message body comes from its own endpoints. Here is the mapping for the calls in the testmail.app documentation.
| Mailsac | testmail.app | |
|---|---|---|
| Authenticate | Send Mailsac-Key: $MAILSAC_API_KEY on every request to https://mailsac.com/api |
JSON API: ?apikey=$TESTMAIL_APIKEY&namespace=$NAMESPACE. GraphQL: Authorization: Bearer $TESTMAIL_APIKEY plus namespace in the query |
| Get a test address | Make one up on your domain: signup-${Date.now()}@yourteam.msdc.co, or @mailsac.com for a public inbox. Nothing to create |
Make up a tag: ${namespace}.signup-${Date.now()}@inbox.testmail.app. Nothing to create |
| List one inbox | GET /api/addresses/{email}/messages. A bare array, newest first; each item has _id, subject, received and links |
GET /api/json?...&tag={tag}, or inbox(namespace, tag) in GraphQL. An object with count and emails[]; each item has id, subject, timestamp, text and html |
| List a test run or a whole domain | GET /api/domains/{domain}/messages for the domain; put the run ID in the address (run42-signup@...) and filter in your test |
&tag_prefix=run42 for one run; the namespace alone for everything |
| Only mail since the test started | Compare each message’s received (an ISO date) with the time you noted; until bounds the other end |
×tamp_from={ms} and ×tamp_to={ms} |
| Wait for the email | Poll the list every 2 seconds against a deadline (below), or attach a webhook or WebSocket to the address and wait on that | &livequery=true: the request is held open and 307-redirected every minute until a match |
| Read the body | GET /api/text/{email}/{messageId} for plain text; /api/body/ for sanitized HTML; /api/dirty/ for the original HTML; /api/raw/ for the whole SMTP message |
text and html fields on the email object; &headers=true adds the headers; downloadUrl for the raw message |
| Extract a link | The links array on the message, found in both text and HTML |
Match it in html or text with a regular expression |
| Extract a code | Match it in your test: text.match(/\b\d{6}\b/). In Cypress, extractCode from @mailsac/cypress |
Match it in your test, as the OTP examples do: text.match(/\b\d{6}\b/) |
| Attachments | GET /api/addresses/{email}/messages/{messageId}/attachments, then /attachments/{identifier} to download |
attachments[] on the email, each with filename, contentType, size and downloadUrl |
| Spam score | The spam field on the message (experimental, 0 to 1) |
&spam_report=true adds spam_score and spam_report |
| Clean up | DELETE /api/addresses/{email}/messages/{messageId}; DELETE /api/addresses/{email}/messages for an inbox; POST /api/domains/{domain}/delete-all-domain-mail for a domain. Or leave it: the oldest messages are recycled at your storage limit |
No delete. Mail expires after the retention period |
| Push to a webhook | PUT /api/private-address-forwarding/{email} with {"webhook": url} on a private address, or turn on forwarding for the domain’s catch-all in the dashboard. WebSockets: wss://sock.mailsac.com/incoming-messages |
No equivalent. Use a live query |
The wait step in TypeScript, with no client library · Node.js 18 or later · filters by received time and subject the way a testmail.app test filters by timestamp_from and tag
const headers = { 'Mailsac-Key': process.env.MAILSAC_API_KEY! };
const address = `signup-${Date.now()}@yourteam.msdc.co`; // was `${namespace}.signup-${Date.now()}@inbox.testmail.app`
const inbox = encodeURIComponent(address);
const startedAt = Date.now(); // replaces timestamp_from
async function waitForEmail(subject: string, timeoutMs = 60_000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`https://mailsac.com/api/addresses/${inbox}/messages`, { headers });
const messages: Array<{ _id: string; subject: string; received: string; links?: string[] }> = await res.json();
const m = messages.find((x) => Date.parse(x.received) >= startedAt && x.subject.includes(subject));
if (m) {
const text = await (await fetch(`https://mailsac.com/api/text/${inbox}/${m._id}`, { headers })).text();
return { id: m._id, subject: m.subject, text, links: m.links ?? [] };
}
await new Promise((r) => setTimeout(r, 2_000)); // no livequery: poll, or attach a webhook or WebSocket instead
}
throw new Error(`No email for ${address} within ${timeoutMs / 1000}s`);
}
const email = await waitForEmail('Please confirm your email');
const code = email.text.match(/\b\d{6}\b/)?.[0]; // one-time code
const link = email.links.find((l) => l.includes('/verify')); // confirmation link
Three habits to keep. testmail.app’s docs recommend a unique tag per test, a timestamp_from filter and a timeout in the test suite rather than the HTTP client. All three carry over: a unique address per test keeps parallel runs and retries apart, the received check ignores older mail (keep the test machine’s clock in sync), and the deadline above replaces the suite timeout. The playwright-signup-ci example does the same in GitHub Actions and GitLab CI, matching on the subject and deleting the message afterwards; the Playwright tutorial also filters by the time the email was triggered.
Public and private inboxes
On testmail.app every inbox is private to your namespace: mail to acmeinc.anything@inbox.testmail.app can only be read with an API key authorised for acmeinc. Mailsac has both kinds. Mail to name@mailsac.com lands in a public inbox that anyone can open at mailsac.com/inbox/name without an account, which is handy for a quick check or a throwaway signup with made-up data, and is why Mailsac does not count mail to public inboxes as Ops. Public mail is kept for up to 4 days and only the most recent few messages in each inbox are kept.
The equivalent of a testmail.app namespace is a custom domain on Indie and up: a yourteam.msdc.co subdomain that is ready immediately, or your own domain once a TXT record and MX records are in place. Every address on it works with nothing to create first, only your account and team can read it, and it is where password-reset links, one-time codes and anything else that works on a real account belong. For a single fixed test account, a private address does the same job; the free plan includes one.
The same rule applies to Email Capture: captured mail is public unless you turn on private capture or capture to a custom domain.
Public inboxes are for synthetic test data. If a reset link or code would work on a real account, or the email holds real personal data, use a private address or a verified private custom domain.
Frequently asked questions
Is Mailsac a free alternative to testmail.app?
Yes, with a different shape. testmail.app’s free plan gives you 100 emails a month in one namespace, kept for 1 day, after a 14-day trial of the paid features. Mailsac’s free plan has no trial period: an API key, one private address, 50 stored messages and 1,500 Ops a month, and mail to any public @mailsac.com inbox is not counted at all. A polled test uses 3 to 6 Ops, so the free plan covers a few hundred test runs a month.
How do Mailsac Ops compare with testmail.app’s emails per month?
They measure different things, so there is no exact conversion. testmail.app counts emails received and rate-limits API requests rather than counting them. Mailsac counts Ops: each message received at a private address or domain, each API call, and each webhook, Slack or WebSocket push. Mailsac’s own estimate is about 3.4 Ops per test email, so Indie’s 25,000 Ops are roughly 7,500 test emails and Business’s 500,000 roughly 150,000. Pushed mail uses one Op per email, and mail read on the website is free.
Does Mailsac have live queries or a GraphQL API?
No. Mailsac’s API is REST. A test either polls the inbox every second or two against a deadline, as the snippet above does, or attaches a webhook or WebSocket to a private address or domain and waits for the push, which uses one Op per email instead of one per poll. There is no GraphQL endpoint; filtering beyond address, domain, date, sender, recipient and subject happens in your test.
Can I use my own domain, or something like a namespace?
Yes. Every paid Mailsac plan includes at least one custom domain: a yourteam.msdc.co subdomain with no DNS work, or your own domain after a TXT record and MX records. Every address on it exists already, the same as a tag in a testmail.app namespace, and GET /api/domains/{domain}/messages lists the whole domain. To group a test run, put the run ID in the address and match it in your test. testmail.app lists custom domains only on its Unlimited plan.
How long is mail kept, and can I delete it?
Mailsac keeps private mail by count, up to your plan’s stored-message limit (1,000 on Indie, 5,000 on Business), recycling the oldest first; starring a message keeps it, and you can delete a message, an inbox or a whole domain over the API. Public mail is kept for up to 4 days. testmail.app keeps mail by time, 1 day on Free, 1 to 3 days on Essential, up to 30 days on Pro and custom above that, and has no delete function in its API or console.
Does Mailsac give a spam score like testmail.app?
Not a comparable one. testmail.app runs SpamAssassin on every email and returns the score and the full rule report, plus SPF and DKIM fields. Mailsac’s message JSON has an experimental spam score from 0 to 1 and no report. If deliverability reports are part of your suite, testmail.app is the better fit for that part.
Which works better in CI with Playwright or Cypress?
Both run the same trigger, wait, check loop over HTTP. Mailsac has a @mailsac/cypress plugin that waits for matching messages and extracts links and codes, a Playwright tutorial against the REST API, and the playwright-signup-ci example that runs in GitHub Actions and GitLab CI. testmail.app publishes Cypress, Selenium and TestCafe examples for both of its APIs, including one-time-code tests. What Mailsac adds for CI is push delivery on every plan and Email Capture for staging apps whose recipients you cannot change; what testmail.app adds is the live query, so a test needs no polling loop.
Try Mailsac with your next email test
Create a free account, generate an API key and run the wait step above against any @mailsac.com address. Plans with your own test domain start at $18 a month.
Other comparisons: Mailsac vs Mailtrap, Mailsac vs Mailosaur, Mailsac vs MailSlurp, Mailsac vs Mailinator, Mailsac vs Mailisk and Mailsac vs Mailpit and MailHog. New to Mailsac? See how the email testing API works, or how to run an email load test.