New Features
- Transactional templates in API v2. Create, edit and delete templates through the API. You edit a draft, publish it, and can view or restore older published versions. To send a template, use Create Template Message. Each recipient can have their own values for the template’s variables.
- Transactional templates in the dashboard. Go to Transactional → Templates. Design a template with the Simple Editor or the Advanced Editor, or upload your own HTML file or a zip file with its images. Each template has its own page. It shows the template ID, its variables, a sample API request, a preview with your sample values, a test send, a default sender and subject, and delivery statistics for the last 30 days. Your edits are saved as a draft. API sends keep using the published version until you click Publish. You can preview and restore the last 50 published versions.
- Campaigns and campaign templates in API v2. Create a draft campaign from MJML, HTML or a campaign template. Then check it, send a test, and schedule or send it. You can also read its statistics and recipients.
- Saved campaign templates. Save a campaign’s design as a template under Marketing → Templates, and start new campaigns from it. Each campaign gets its own copy. If you change the template later, campaigns you already made do not change.
- Contacts and lists in API v2. Manage contacts, add many contacts to lists at once, and see who is on each list.
- Sending type for domains. Each domain is now either transactional or marketing. Pick the type that matches the email you send from it. See which sending type to use.
- One-click unsubscribe. Email from a marketing domain gets the
List-UnsubscribeandList-Unsubscribe-Postheaders, so mailbox providers can show an unsubscribe button. To add an unsubscribe link to the body of any email, put{{unsubscribe_url}}in it. - Daily marketing limit. Each account has a daily limit for marketing email. A campaign that goes over it continues on the next days. Marketing email sent through the API or SMTP that goes over it waits about a day, and then fails.
- Paused domains. If too many recent emails from one domain bounce, AhaSend can pause just that domain. Your other domains keep sending. A parent account can unpause a sub-account’s domain.
- New API key scopes:
contacts:*,lists:*,campaigns:*,templates:writeandtemplates:delete. Keys with*get all of them. Keys with thetemplatesscopes can also manage campaign templates. - “View in browser” link for campaigns. Add it from the variable menu in the editor. After the campaign is sent, the link opens the email as the recipient got it. Opens and clicks on this page are not tracked.
- Right-to-left emails. Both editors can set the text direction of the whole email or of parts of it. Use this for Arabic, Hebrew, Persian and other right-to-left languages.
- Campaigns, contacts and lists for every account. The dashboard has a new sidebar with four sections: Transactional, Marketing, Developers and Settings. Old links and bookmarks still work and open the new pages.
- Dashboard times in your time zone. Charts, daily totals, reports, date filters and CSV exports use the time zone in your profile (Profile → Timezone). If you have not set one, they use UTC. Billing periods stay in UTC.
- Go SDK v0.5.0 and Node.js SDK
@ahasend/sdk0.5.0. Both SDKs support contacts, lists, segments, campaigns, campaign templates, transactional templates and template sends. They also support domain sending types, unpausing a sub-account’s domain, the new webhook settings and events, and Simple Editor designs in AhaML. Install them withgo get github.com/AhaSend/ahasend-go@v0.5.0ornpm install @ahasend/sdk@0.5.0. - AhaSend CLI v0.3.1. v0.3.0 adds the
contacts,lists,templatesandcampaignscommands, and builds for Linux and Windows on ARM64. v0.3.1 does not change any command. You can install it with Homebrew, Scoop, npm, an install script, or a .deb or .rpm package. See Installation. - MCP server for AI tools. Connect Claude Code, Claude.ai, ChatGPT, Cursor, VS Code, Codex, Mistral Vibe and other AI tools to your account at
https://mcp.ahasend.com/mcp. The AI tool can then send email, find out why an email failed, set up domains, build templates, and manage contacts, lists, segments and campaigns. You do not need to create or copy an API key. You log in in your browser, pick the account (sub-accounts work too), and pick what the AI tool may do. Use one of the presets (Read-only, Developer, Marketer or Full) or pick single permissions. You can also limit the connection to some domains, or to sandbox sending only.- Each connection is an API key. The API Keys page shows the tool and the user who connected it. You can change its permissions or delete it there at any time.
- The AI tool cannot read your received email unless you allow it for that connection. A campaign is sent only after you confirm it in your AI tool.
- A connection stops working when its user loses access to the account, or when the tool has not used it for 30 days.
- The Connect an AI tool button on the API Keys page shows the setup steps for each tool. Cursor and VS Code can install it in one click.
- SMTP on port 465.
send.ahasend.comnow accepts SMTP on port 465 with implicit TLS. This is for email clients and devices that cannot use STARTTLS. Port 465 supports AUTH PLAIN and AUTH LOGIN. Ports 25, 587 and 2525 with STARTTLS work as before. The SMTP credential page also shows port 465. - Save a list search as a new list. On a list page, search or use advanced search, then click Save as new list. The matching contacts are copied to a new list, which you can send a campaign to. Each contact keeps its status, so unsubscribed contacts stay unsubscribed on the new list. The copy runs in the background, and the new list’s page shows when it is done. The new list does not follow the original: later changes to the original list do not reach it.
- AhaSend Markup (AhaML) for Simple Editor designs. The API can read and write Simple Editor designs as text, in AhaML, in the
content.markupfield. This works for transactional templates, campaign templates and campaigns. Read a design, change it and send it back, and the dashboard editor opens what you wrote. If a write is refused, the error lists each problem with its line and column. A design with an HTML block cannot be shown as AhaML, so its read returns amarkup_error. - Get Published Template. A new endpoint that returns the version of a template that sends use, with the variables each recipient must give. Use it when you build a send.
- Segments. Go to Marketing → Segments to group contacts by rules. Rules can use contact fields, the signup date, list membership, and the campaigns a contact got, opened or clicked, and how many. They can also match birthdays and anniversaries on date fields: a month, or a date in the next few days. A segment updates by itself when your contacts, lists and campaigns change. You can manage segments through API v2 too, with the new
segments:read,segments:writeandsegments:deletescopes. - Send campaigns to segments, and leave contacts out. A campaign can go to lists and segments together, and leave out the contacts of other lists or segments. A contact who is in more than one of them gets the campaign only once. A campaign sent to a segment reaches the segment’s contacts who are confirmed on a list or are not on any list.
- Contact webhooks. New events tell your systems when contacts change:
contact.created,contact.updated,contact.deleted,contact.added_to_list,contact.removed_from_list,contact.subscribed,contact.unsubscribedandcontact.complained. A webhook created through the API needs a key that can read contacts. For the list events, the key must also be able to read lists. - Unsubscribe and spam report webhooks for campaigns.
message.unsubscribedandmessage.complainedtell you which campaign email made someone unsubscribe or report it as spam.message.unsubscribedalso says how they unsubscribed: with the one-click button or with the link. - Suppression webhooks for every change.
suppression.createdis also sent when you add a suppression through the API, and when someone unsubscribes from a transactional email. The newsuppression.deletedis sent when a suppression is deleted. Both events say who made the change, in theoriginfield. - Choose whose changes a webhook gets. Use a webhook’s
originssetting to get contact and suppression events only for changes made by recipients (recipient), through the API (api), in the dashboard (dashboard) or by AhaSend (system). For example, leave outapiso you do not get events for changes your own code made through the API. - CSV imports warn you about values in quote marks. If the values of a column are wrapped in quote marks, which can happen when you export a JSON column, the import tells you which column, with an example. The values are saved as they are, so rules that compare them may not match.
Changes
- Breaking: Get Template returns the working copy. This is the draft if the template has one, and the published version if not. So you can read a template, change it and send it back with Update Template. While a draft exists, its
updated_atcan be different from the one List Templates shows. To build a send, use Get Published Template. The draft endpoint,GET /v2/accounts/{account_id}/templates/{template_id}/draft, is removed. Discard Template Draft still works. - Breaking: API v2 HTML-escapes substitution values in the HTML and AMP bodies, as v1 and campaigns already do. For example, a value of
<b>Ann</b>shows as that text, not as bold text. To insert a value as HTML, write{{ name|safe }}in your content. If your code escapes values itself, stop, or they are escaped twice. The subject, the headers and the text body get the values unchanged. See substitutions. - Breaking: AhaSend CLI v0.3.0 changes exit codes, input checks, prompts, flag defaults and output. If you have scripts that use v0.2.0, read the release notes before you upgrade.
- API v2 refuses some headers with
400. This happens for a custom header name that is not valid, for example one with a space or a colon. It also happens for a substitution value with a line break that would end up in a header, such as the subject or a display name. - API v1 and v2 drop the custom headers
X-Campaign,X-CUSTOMER-ID,X-Subscriber-IDandX-Schedule-ID. The send still works, without them. A v1 sandbox key always sends sandbox email, whatever headers the request has. - A campaign needs an unsubscribe link before you can schedule it. Put
{{ unsubscribe_url }}in its design. This applies in the dashboard and in the API. Campaigns that are already scheduled or sending are not affected. - A spam report about a campaign sent to a list unsubscribes the contact from that list. It does not add a suppression, so transactional email to the address keeps going. A spam report about transactional email still suppresses the address.
- API error messages say “paused” instead of “suspended” for accounts and domains.
- Campaign reports count only bounced and failed recipients as bounces. Email that expires before AhaSend ever tried to deliver it is marked as failed, and does not count as a bounce.
- Campaign open and click rates count unique recipients out of delivered emails, and leave out bot opens and clicks. Before, the click rate was clicks divided by opens, so it could go over 100%. Reports of older campaigns show the new rates too.
- A list’s subscriber count shows only the contacts a campaign can reach. Bounced, invalid and unsubscribed contacts are left out, in the dashboard and in the API’s
contact_count. The Lists page also shows the total number of contacts on each list. - Unsubscribes and spam reports are protected suppressions. Wiping suppressions keeps them, and the dashboard marks them as protected. You can still delete them one at a time.
- Renaming a contact field changes only its label. Its key stays the same. Templates, saved searches and the API use the key.
- Pause notices explain why. When an account is paused, the dashboard and the email give the reason and the next steps.
- Some campaign actions are refused earlier, with a message that says why: scheduling less than a minute ahead, scheduling when the sender domain is not verified or is paused, and scheduling when no contact on the chosen lists can get the campaign. A reply-to address must be a plain address, without a name.
- Messages record their template. In API v2, a message sent from a transactional template has its
template_id. - The contact CSV export writes dates in your time zone, with the UTC offset. If your own code reads this file, check how it reads dates.
- You can no longer create legacy (v1) API keys. Existing v1 keys keep working. Use API v2 keys for new integrations.
- An account name made only of spaces is refused, in onboarding and on the account page.
- API keys have a new permissions screen in the dashboard, the same one as the MCP connection page. When you create or edit a key, each group of permissions has a No access, Read or Write switch. Click a group to see its single permissions. Quick buttons set Full access, Sending only or Read-only. Each permission that applies to sending domains can be allowed for all domains or for the domains you pick, with a search box when the account has many domains. If you choose “only these domains” but pick no domain, the key is refused.
- Saving an API key removes permissions that no longer apply: permissions for a domain that was removed from the account, and for a feature the account no longer has. The edit page lists them before you save.
- API v2 refuses some display names with non-ASCII characters, such as é or ü, on sends with substitutions or a template. When
from.name,reply_to.nameor a recipient’snamehas non-ASCII characters, AhaSend fills in plain variables such as{{ first_name }}itself. Any other template code in such a name, such as a filter or a tag, fails the send with400. A transactional template whose default sender name breaks this rule cannot be saved. Names with only ASCII characters are not affected. - API v2 drops a custom
AhaSend-Toheader, and Create Conversation drops a customToheader. TheToheader comes from thetofield. - The SMTP credential page shows one server,
send.ahasend.com.send-us.ahasend.comstill works. - API v2
429(rate limit) responses have aRetry-Afterheader. It tells you how many seconds to wait before you try again. See rate limits. - API v2 checks every message with the template engine before it sends it. Create Message, Create Template Message, Create Conversation and Test Campaign answer
400for content that cannot be filled in with a recipient’s values, and send nothing from that request. If the check cannot run right now, they answer503with aRetry-Afterheader. Send the same request again after that time. See API errors. - A transactional template’s
variablessay what to send for each variable. Each variable has akind:valuefor text in the email,conditionfor a value that shows or hides a part of the email, andlistfor an array that repeats a part. For alistvariable,item_fieldsnames the fields of each item. See kinds of variables. - The campaign issue
text_uncheckedmeans the campaign could not be checked right now. Nothing is known to be wrong with it. Check it again in a moment. - Unsubscribing from a campaign sent to a segment unsubscribes the contact from all your marketing email. A spam report does the same. This is because a segment is not a list the contact joined. Unsubscribing from a campaign sent to a list still removes the contact from that list only.
- Contact dates are read day first. Imports and the dashboard read
05/12/1990as 5 December 1990, not 12 May. A date that only works month first, such as12/25/1990, is no longer saved. Dates such as1990-12-05are read as before. - The Simple Editor no longer lets you add HTML blocks. HTML blocks that a design already has still show, send, and can be edited or deleted.
- Opening the logout address asks you to confirm. It shows a page with a Log out button. The Log out item in the menu works as before.
Improvements
- The old DKIM key is revoked one week after a key change. For domains with managed DNS, AhaSend publishes an empty key in place of the old one and deletes the old private key, so the old key can no longer pass DKIM checks. Domains where you set up DKIM yourself are not affected.
- Campaign list checks finish faster on large lists, so the send decision is based on more checked contacts.
- Campaign list checks no longer count addresses on catch-all domains as bad. A catch-all domain accepts email for any address, so the check cannot tell if the mailbox exists. These contacts still get the campaign. The list check on the campaign report shows them as Catch-all, next to Risky. A campaign to a large list where most addresses are on catch-all domains goes to manual review.
- Campaign report breakdowns load faster for large campaigns. They are counted in the background, and show when they were last updated. The bounce and status lists have Previous and Next buttons, and no recipient shows twice or is skipped.
- Advanced search on a list finds more. Full names match, “is empty” also finds contacts that never had the field, and date filters accept relative dates such as “7 days ago”.
- See a sent campaign’s full email. On a sent campaign’s page, the design preview has a View email button that opens the email in a new tab.
- CSV contact imports accept an
Email Addresscolumn, as in Mailchimp exports. Before, the column had to be calledemail. From a Mailchimp export, import only the file of subscribed contacts. The unsubscribed and cleaned files have no status column, so their contacts would be imported as subscribed. - When MJML cannot be built, the Advanced Editor tells you why, with the error from the MJML compiler.
- Dark mode is easier to read. Links and blue buttons, including disabled ones, are lighter, and selected text is visible. This applies in dark mode and on the dark sidebar and page headers. The stat cards on a campaign report all have the same height.
Bug Fixes
- Fixed Reputation Shield blocking email to real email providers, such as Hotmail, Outlook, Live, MSN, Proton and Tutanota, for a short time in early October. A public list of throwaway email domains that we use had started to include them.
- Fixed automatic DKIM key changes starting late for some domains, mostly on plans that change keys more often.
- Fixed API v2 always showing managed DKIM records as
propagated: false, even on domains that were fully set up. - Fixed managed DNS records sometimes showing “Issue detected” for up to 15 minutes after we published them. This happened when our DNS provider briefly limited our requests.
- Fixed campaign list checks counting addresses they could not check as bad. This happened when the checking service was busy, when the check took too long, or when the receiving mail server asked us to try later or blocked the check. These contacts keep their status, still get the campaign, and a later list check can check them again.
- Fixed many spam reports from Yahoo and Microsoft not being recorded. These reports now show in your statistics, and the addresses are handled as described above.
- Fixed a bounce or a spam report undoing a contact’s unsubscribe from a list. Adding many contacts at once or importing a CSV again also no longer re-subscribes people who left a list.
- Fixed updating a webhook through API v2 failing with
500for webhooks limited to some domains. This happened from 5 to 12 October. - Fixed campaign report exports (CSV and PDF) showing “not found”.
- Fixed advanced search on the list page not applying its filter.
- Fixed the Send Campaign button on a list page showing “not found”. It now starts a campaign for that list. If you searched the list first, the campaign window tells you that the campaign goes to the whole list.
- Fixed not being able to remove a campaign’s reply-to address.
- Fixed the average delivery time cards on the home page showing 0 for accounts with no delivery window set.
- Fixed changing a domain’s settings through API v2 not updating its
updated_at. - Fixed API v2 sends saying recipients were sent when they were not. This happened when the client closed the connection during the request. A retry with the same idempotency key got the same wrong answer. Now a send always finishes once it has started. This also applies to template sends.
- Fixed the API key form losing your choices when a key was refused. The form closed, and when you opened it again it started from Full access, so you could create a key with all permissions by mistake. Now the form opens again with the name, IP allow list, permissions and sending mode you entered, and shows why the key was not created.
- Fixed IP allow list changes being refused again and again after a save that asked you to confirm it was you. The form did not ask for the confirmation a second time. This affected the API key and SMTP credential forms.
- Fixed long sender names with non-ASCII characters getting an extra space in API v2 sends, in
FromandReply-To. For example, “Gymnasiums” arrived as “Gym nasiums”. - Fixed non-ASCII names in
toon Create Conversation being sent without encoding. Some email providers, such as GMX, rejected these emails. - Fixed API v2 attachments losing their file name when the request set
content_disposition. - Fixed MJML with self-closing tags, such as
<mj-image ... />, failing with “Failed to render MJML” in the Advanced Editor. MJML exported from Klaviyo uses these tags. - Fixed
ahasend routes listen --recipientanswering403for a domain that is not in the account. It now answers400, with a message that says the domain was not found in the account. - Fixed an open or a click changing a bounced email back to “Opened”. A later open also no longer changes a clicked email back to “Opened”.
- Fixed bounce reports from other mail servers being matched to emails by headers that anyone can fake. A report now matches only an email that AhaSend delivered, and suppresses only that email’s recipient. Real bounce reports that we missed before are now matched.
- Fixed some bounced emails losing their “Non-delivery report” link when the bounce report had no reason text.
- Fixed a campaign list check in one account blocking the same address in other accounts.
- Fixed
ahasend webhooks listenandahasend routes listenshowing events that the API key is not allowed to see. - Fixed open and click webhooks ignoring the webhook’s domains. A webhook set to some domains got the opens and clicks of all your domains. Click webhooks for campaign links were also never sent.
- Fixed
suppression.createdbeing sent again when an address that is already suppressed bounces or unsubscribes again. - Fixed campaign webhook events being lost after the campaign’s domain was deleted.
- Fixed a transactional template’s
variablesmissing names that are only used in show/hide rules or repeat blocks. - Fixed the Simple Editor’s HTML block preview cutting off wide emails, or staying empty while a slow image loaded. The preview does not run scripts.
- Fixed a paused account not being able to set the sending type of its domains, which the pause notice asks it to do.
Changes
- The account and sub-account
websitefield now accepts a domain or an http(s) URL everywhere: the dashboard, onboarding,PUT /v2/accounts, and the sub-account create and update endpoints. The value is stored as the bare domain, sohttps://acme.com/teamis saved asacme.com. Values that are not web addresses (such asftp:,mailto:orjavascript:) and hosts that are not valid domain names are now rejected. Existing websites are not changed until they are next edited. - Inbound routes created through the API now get the same signing-secret format as routes created in the dashboard:
<prefix>-rsec-followed by 64 characters that may include letters, digits,_and-. If your code checks the length or characters of route secrets, update it to accept this format. Existing secrets are unchanged and keep working. - The
AhaSend-Scheduleheader is now reserved in API v2 sends. If you include it in a message’s customheaders, it is dropped. To schedule a message, use the API’s scheduling fields. - Messages that expire after running out of retries now suppress the recipient only when the receiving side caused the failure. A message that expires without ever being attempted no longer suppresses the address. Messages to domains that really don’t exist are still suppressed.
- After you create an API key, its secret is shown once and is no longer placed in the page address, so it does not end up in your browser history (previously it was included in the URL). Reloading the page will not show it again, so copy it when it first appears.
Improvements
- Validation errors from
PUT /v2/accountsand the sub-account endpoints are now clearer. They name the JSON field, list every problem in the request and say what to send, instead of a generic “invalid request body” or internal validator text. - Delivery events are recorded more reliably during heavy sending. Events are confirmed only after they are stored, so they are no longer lost during service restarts or traffic spikes.
- The message details page now keeps explaining why a message is waiting, for example that your account has reached its sending rate limit, for as long as it waits. Before, the explanation could disappear after a few minutes.
- The cookie consent banner on the dashboard is now served from AhaSend’s own domain, so it loads even when a third-party CDN is blocked.
Bug Fixes
- Fixed temporary failures on AhaSend’s side adding valid recipients to your suppression list and disabling campaign subscribers. These messages still show as bounced. Recipients are now suppressed only when the receiving mail server or the recipient’s own domain rejects the address.
- Fixed spam complaints from mailbox providers not being processed when the original email used a character set other than UTF-8. Recipients who complain are now suppressed and unsubscribed from your lists as intended.
- Fixed domains that point their inbound (MX) host at AhaSend with a CNAME showing the MX record as not configured and being unable to create inbound routes. Inbound mail to these domains kept arriving throughout.
- Fixed a short DNS lookup failure marking a correctly configured domain’s records as missing. This could briefly mark a domain as unverified and pause its sending or receiving. We now keep the last known status until DNS gives a clear answer, and try several resolvers before deciding a record is really gone.
- Fixed the checkout pay button spinning forever when an ad blocker or browser tracking protection blocks Stripe’s payment form. You now see a clear message explaining what to do.
- Fixed choosing a plan on the Billing page sometimes failing because two checkout sessions were started at once.
- Fixed resending a verification email, requesting a password reset and changing your email address showing “Internal server error” when the request had actually succeeded. Expired-link messages now say a new email was sent only when one really was.
New Features
- Official TypeScript / JavaScript SDK:
@ahasend/sdkis now available on npm (npm install @ahasend/sdk). It covers the full API v2: messages, domains, API keys, webhooks, statistics, suppressions, inbound routes, SMTP credentials, account members and sub-accounts, with TypeScript types included, automatic idempotency keys on create calls, retries, rate-limit handling and pagination helpers. A separate@ahasend/sdk/webhooksentry point verifies webhook signatures and parses all event types, with adapters for Express, Fastify and Next.js. It runs on Node.js 22+, Deno, Bun, Cloudflare Workers and Vercel Edge. It is a server-side SDK and is still v0.x, so pin an exact version in production. - Delivery Attempt Details in Webhooks:
message.delivered,message.bouncedandmessage.transient_errorwebhooks now include adelivery_attemptobject with the SMTP result behind the event:smtp_code,enhanced_status_code, the receiving server’sresponse, a plain-Englishdescription, the bounceclassification, and the SMTPcommandstage. The field is additive and is left out of events that are not the result of a delivery attempt. Test webhooks and sandbox sends include a representative sample, so you can build against the real payload shape. - See Why a Message Is Still Waiting: The message details page now explains why an outbound message has not been attempted yet: that it is scheduled for a specific time (shown in your timezone), that your account has reached its sending rate limit, that deliveries to the recipient’s mailbox provider are temporarily paused, or that deliveries are being paced.
- Find Messages That Were Ever Deferred: A new “Has deferred attempts” filter on the Messages page finds messages that hit a temporary delivery failure at any point, even if they were delivered afterwards.
- Readable Delivery Diagnostics: When a delivery problem happens on our side of the connection, the delivery attempts on the message details page, campaign bounce messages and the campaign recipient export now show a clear sentence instead of raw mail-server internals. For example “DNS lookup for the recipient domain gmail.con failed: NXDOMAIN (the domain does not exist)” or “The destination mail server did not respond to MAIL FROM within 10s”. Responses from recipient mail servers are still shown exactly as received. Applies to newly logged attempts.
Changes
- Outbound messages waiting for their first attempt now show as “Queued” in the dashboard instead of “Received”. Inbound messages still show “Received”. API responses and webhooks are unchanged.
- The Messages status filter now offers “Queued” and “Received (inbound)” in place of the single “Received” option, and the separate Direction filter has been removed. Saved links that filter on
status=Receivednow return inbound messages only; usestatus=Queuedfor outbound. - Message status search is now cumulative, in both the dashboard and API v2. Filtering by
Deliveredalso returns messages that have since been opened or clicked, andOpenedalso returns clicked messages. Integrations filtering onstatus=Deliveredwill see more results than before. - Campaign messages that run out of delivery retries are now reported as Failed and trigger a
message.failedwebhook, matching transactional messages. Previously they were reported as Bounced withmessage.bounced. If you handle campaign webhooks, make suremessage.failedis processed. Bounce totals and bounce rates in campaign reports are unchanged. - API v2 now returns
409 Conflictfor duplicates: when adding a team member who already belongs to the account (previously500), and when creating a domain that already exists (previously400). Update any integration that checks for the old status codes. - The per-recipient
errortext on API v2 sends has been reworded. Numeric codes are kept, and521account-status messages are unchanged. The556“sending domain not allowed” error now explains that a newly added domain can take up to 2 minutes to propagate. Unexpected internal failures return a generic message instead of internal details. If your integration matches on exact error text, please review it. - SPF validation now mirrors how receiving mail servers read your record. An AhaSend include placed after
~allor-all, given a~,-or?qualifier, placed after a broken include, or pushed beyond the 10-DNS-lookup limit is no longer reported as configured, since receivers would not honor it either. - Suppressions created from mailbox-provider spam complaints now use the reason
FBL Complaint Reportinstead ofFBL Abuse Report, in the dashboard, the APIreasonfield and thesuppression.createdwebhook. Existing suppressions keep their original text, so integrations matching on the exact string should accept both. - The (somewhat broken) Re-check DNS button was removed from the domain list. DNS checks are run from each domain’s own page.
Improvements
- SPF validation now follows nested
include:chains andredirect=, so domains that authorize AhaSend through an intermediate SPF record are correctly recognized as configured. - SPF status is steadier during DNS trouble. A temporary failure looking up your own domain no longer flips SPF to unconfigured, and an outage at a third-party SPF provider can no longer freeze a stale status.
- DNS setup instructions now explain where the AhaSend include belongs: before the final
~allor-allof an existing SPF record. - When several of a recipient domain’s mail servers all fail, the delivery log now sums up the outcome in one sentence, with SMTP codes, rejection text and attempt counts, instead of a long chain. Temporary failures also say that another attempt has been scheduled.
- Test sends now report results per recipient, for both transactional templates and campaigns. You see which addresses were accepted and a readable reason for any that were rejected. If the result cannot be confirmed, you are told to check the message log rather than to resend.
- Failed campaign recipients now always show a failure reason in the campaign report’s recipient list (for example “Inbox Out of Storage”) instead of only after several attempts.
- The API v2 list messages endpoint now accepts
OpenedandClickedasstatusfilter values. They were previously rejected as invalid. - The Lists page loads faster and more reliably for large audiences. Subscriber counts for filtered lists are now calculated far more efficiently.
Bug Fixes
- Fixed transactional
message.clickedwebhooks always reportingis_bot: falseand an emptyuser_agent. Both now carry the real values recorded at click time. - Fixed campaign open webhooks not reporting the bot-detection result.
- Fixed cancelling a scheduled message through API v2, which returned a
500error instead of cancelling the send. Cancellation now also covers messages from domains on a dedicated IP pool and sandbox messages. - Fixed scheduled sends through the conversational message endpoint being delivered immediately. The
scheduleis now honored. - Fixed delivery events for campaign emails being recorded as transactional messages when the campaign was sent from a domain on a dedicated IP pool or in sandbox mode.
- Fixed a rare condition in which a campaign batch could be queued a second time if the sending server’s confirmation could not be read, which could have caused duplicate campaign emails.
- Fixed a late temporary-failure event moving a campaign recipient back to “Deferred” after it had already been delivered, failed, suppressed, unsubscribed or marked as a complaint. A late temporary failure on an already-bounced recipient now sends
message.transient_errorinstead of a duplicatemessage.bounced. - Fixed campaign test sends reporting success when the recipient had actually been rejected, and test sends delivering duplicate copies when the same address was entered more than once.
- Fixed campaign recipients rejected at queueing time showing raw internal error text or “Unknown error” in the campaign breakdown and export.
- Fixed test webhooks triggered through the API sending a malformed event
type(such asmessage.message.delivered) anddata.eventvalue. They now match real payloads. - Fixed team members removed through the API not being able to be added back to the account.
- Fixed the “Full Access” preset in the API key dialog silently leaving out the “Wipe All Suppressions” permission (
suppressions:wipe). Keys created earlier with this preset are unchanged; edit them to add the permission if you need it. - Fixed the same domain staying verified in more than one account at once. When a domain is verified, a copy in another account that has stopped validating and uses the same DKIM selector is now un-verified. Domains that are actively validating, or that use a different DKIM selector, are never affected.
- Fixed the API v2 domain DNS check when a domain is deleted while the check is running. It now returns
404 domain not found, and the dashboard shows a clear message. - Fixed whitelabel accounts being shown the default AhaSend MX host in the Incoming Routes setup instructions and MX error messages, instead of their own MX hostname.
- Fixed return-path lookups failing for domains or return-path subdomains entered with upper-case letters, which could prevent delayed bounce reports from being matched to the right domain.
- Fixed a rare condition where an open or click on a campaign whose sending domain had been deleted could briefly interrupt open and click tracking.
- Fixed the account settings descriptions for rejecting mistyped and temporary-mail recipient domains to quote the exact
553error text those features return.
New Features
- CSV Contact Import: You can now fill a contact list by uploading a CSV file — from the Contacts page, from an individual list, or directly in the Create List dialog so a new list is created and populated in one step. Contacts that already exist are updated and added to the list; new ones are created. Recognized columns are email, first name, last name, status, and any of your custom contact attributes.
- Background Imports with Progress and Email Notification: Files above roughly 500KB or 5,000 rows are processed in the background. The Contacts and list pages show a live progress banner with a Cancel button, and you receive an email when the import finishes reporting how many contacts were imported, how many joined the list, and how many rows were skipped. If an import fails, the email explains why whenever the cause is something you can fix — such as a malformed row and its line number, a missing email column, or an empty file — and re-uploading the same file is always safe.
- Higher Hourly Sending Limits, Graduated by Plan: Hourly sending limits start low for new accounts and grow with account age, plan and sending reputation; support can raise them. See current limits. Higher rate limits are possible after a manual review and approval, please contact us if your sending patterns require higher rate limits.
- Per-Service Cookie Controls: “Manage preferences” now lets you allow or refuse individual services rather than only broad categories — PostHog analytics and UTM attribution under Analytics, Trackdesk affiliate tracking under Marketing. Refusing a category or service now actively deletes the cookies belonging to it, and withdrawing consent takes effect immediately in every open tab.
Changes
- Contact email addresses are now case-insensitive. Two spellings of the same address are now the same contact everywhere — on import, when adding a contact, in search, and in list segments. Existing contacts that differed only by capitalization have been merged into one, keeping the most restrictive state (unsubscribes, suppressions, and invalid-address verdicts always survive) and preserving names, custom attributes, list memberships, and engagement history. Previously, re-adding an address with different capitalization created a second contact that could bypass the original’s unsubscribe.
- Saved list segments with email rules were updated. Segments filtering on an email address with “equals” or “does not equal” have had their values lowercased so they keep matching. “Contains”, “begins with”, and “ends with” rules were left exactly as you wrote them; they already ignore capitalization.
- Campaigns now skip globally unsubscribed contacts. They are excluded when a campaign is initialized, again before each batch is sent, and in pre-send list validation. The rejection reason shown on a campaign report changed from “disabled or invalid” to “unsubscribed, disabled, or invalid”.
- Creating a list with no filter now creates an empty list. Previously, creating a list without any filter criteria added every contact in your account to it. To include all your contacts, add or import them explicitly.
- Route webhook attachments carry a new
dispositionfield. Each attachment now reports whether it was sent as a conventional attachment, as an inline part embedded in the message body, or with no disposition specified, so your integration can tell a genuine file attachment apart from an embedded image. The field is also present when receiving route events over WebSocket. - The per-message delivery attempt log now lists the final outcome last among attempts recorded in the same second, in both the dashboard and the
delivery_attemptsfield of API v2 message responses. - CSV imports accept files up to 100MB, with a clear message in the browser if you pick something larger.
- The account suspension email now names the affected account and its account ID, and links directly to it in the dashboard — useful if you manage sub-accounts and receive notifications for more than one account.
- The inbound routes page now shows MX setup guidance: Routing is not possible unless your (sub)domain’s primary MX record is pointing to AhaSend. The dashboard now has an improved setup guide on the routes page telling you to do so before you can setup routes.
Improvements
- Accounts approved for marketing are no longer penalized for sending marketing. Our automated content review previously treated all marketing content as a policy violation and scored it at maximum regardless of whether the account was permitted to send it. That decision now respects your account’s marketing setting.
- Imports never re-subscribe anyone. Contacts who have unsubscribed globally are not added to the target list, and existing list memberships — including unsubscribed ones — are left as they are. Rows with an invalid email address are skipped and reported rather than imported.
- Sending-pool assignment now reflects the whole umbrella for Platform Partners. A partner account’s reputation tier is calculated from the combined volume and spam rate of itself and all of its sub-accounts rather than only its own traffic, and a sub-account inherits its parent’s account age and subscription standing. Sub-accounts of established partners are now routed on reputation-appropriate IPs from their first send instead of defaulting to the neutral pool. Sub-accounts are still assessed on their own spam rate for demotion, so one bad sub-account neither drags down the umbrella nor is shielded by it.
- Images in marketing campaigns load faster on repeat views. Cached campaign images now tell recipients’ email clients that they can be kept for 24 hours, matching what images already got on first load.
- Links inside notification emails are now visibly styled as links instead of blending into the body text. This covers account suspended, credit limit warnings, domain DNS problems, and disabled webhook, routing, and S3 integration notices.
Bug Fixes
- Late and retried delivery events now report what actually happened instead of repeating the last known status. Our mail server can retry an event notification for up to 24 hours. A retried reception, temporary failure, or bounce arriving after a message was already delivered previously re-sent a duplicate
on_delivered; it now sends the event that actually occurred. A retried non-delivery event on a message you had already opened or clicked used to produce no webhook and now reports itself. A retried delivery event on an already-opened message still sends nothing, and an expired message still produces exactly oneon_transient_errorand oneon_failed. Payload shapes are unchanged and no new event types were added. - Contact Imports no longer overwrite data you didn’t include. A CSV that omits a column, or leaves a cell blank, now leaves the existing first name, last name, and custom attributes untouched, and custom attributes are merged rather than replaced. Previously an email-only CSV would erase names and attributes on every contact it touched.
- Fixed expired messages permanently showing as “Deferred”. When a message ran out of delivery attempts, the final “expired” event and the last temporary-failure event carried the same one-second timestamp, and whichever arrived first won — so the message could stay “Deferred” indefinitely in the dashboard and API, carrying the wrong bounce classification and never counted as failed. A message’s status is now always taken from its terminal outcome regardless of event ordering. Reporting and aggregate statistics were never affected, and neither were campaign recipients.
- Fixed errors when viewing or listing a webhook scoped to a sending domain that was later deleted.
- Fixed inbound route webhooks silently dropping attachments. Routes with attachment forwarding enabled were discarding images embedded inline in the message body, and files whose sender omitted the content-disposition header — a common pattern for automatically generated CSV reports. Both are now delivered. Expect larger payloads if your route receives mail with embedded images.
- Fixed the single-message API endpoint omitting inline attachments from its parsed content; embedded images are now included with their filename, content type, content ID, and contents.
- Fixed the team member API endpoints returning an all-zeros
user_id. Listing members returned the placeholder for every member, and adding a member returned it whenever the invited address did not already have an AhaSend user. The value can now be used directly with the remove-member endpoint. - Fixed DKIM records with optional tags failing validation. If you publish your DKIM record manually and your DNS provider adds or reorders tags such as
h=sha256, your domain showed DKIM as unconfigured despite a valid record. Validation now checks the required tag values and ignores extra ones, while still rejecting a wrong public key or a malformed tag list. - Fixed notification emails never being delivered to team members whose name contains a non-ASCII character. Credit-limit warnings, webhook, route and domain disabled alerts, suspension notices, API key security alerts, and campaign review notifications were silently dropped for those recipients.
- Fixed display names in the
Ccfield of a conversational message being mangled when they contained non-ASCII characters, and an emptyCc:header is no longer added to messages sent with no Cc recipients. - Fixed adding contacts to a list from the dashboard silently doing nothing. Selecting contacts and adding them to a list failed to add anyone; it now works, including re-subscribing a contact who had previously unsubscribed from that list.
- Fixed CSV files exported from Excel as “CSV UTF-8” being rejected as missing an email column; the hidden marker these files begin with is now handled.
- Fixed renaming a contact attribute converting every stored value to text. Renaming a number, true/false, or date attribute could cause segment filters on it to stop matching; values now keep their original type.
- Fixed editing a contact attribute’s display name being rejected as a duplicate when only its capitalization or spacing changed. It now saves, and does so without rewriting your whole contact book.
- Security: fixed two open-redirect issues in the dashboard. A crafted link to the billing checkout-return page could redirect a signed-in user to an external site, and the re-authentication screen could be tricked into the same via a specially encoded referring page. Both now only accept destinations inside the dashboard. No customer data was accessible through either issue, and we have no indication of exploitation.
- Fixed an explicit cookie rejection being silently overridden. If you are outside a jurisdiction where a consent banner is legally required and you had rejected cookies, your choice was being reset to “accept all” on each page load. It is now respected. Stale dashboard-only consent cookies are also cleaned up automatically so they cannot override your current choice.
- Fixed changing your account email address: entering a different capitalization of your current address is now recognized as no change, and confirming a change to an address another user already holds now shows a clear error instead of appearing to succeed.
New Features
- Sub-Accounts: Platform Partner and reseller accounts can now create and manage sub-accounts under a single parent, from both the dashboard and the v2 API. Each sub-account is a full account — its own domains, sending, webhooks, routes, and members — that is billed through the parent, with usage pooled onto the parent’s invoice and a proportional per-sub cost breakdown available in the dashboard and API.
- API-Only Sub-Account Provisioning: Partners can provision a new sub-account and mint its first working API key entirely over the API, with no dashboard step. The new key belongs to the sub-account, and its secret is returned only once (or on an exact idempotent retry within five minutes).
- API Key IP Allow Lists: Each v2 API key can now be restricted to a set of IPv4/IPv6 addresses or CIDR ranges. When a key has a non-empty allow list, any request made with it from an IP that isn’t on the list is rejected on every endpoint, regardless of the key’s permissions. An empty list means no restriction, so existing keys are unaffected. Up to 100 entries per key; the “allow everything” ranges are rejected.
- Per-Domain DKIM Selectors: Platform Partner accounts can now set a custom DKIM selector for each sending domain — in the dashboard and via the v2 API — instead of only an account-wide selector, so the same domain can be authenticated across multiple senders without collisions. The selector can be changed while the domain’s DKIM is unverified and locks once verified (contact support to unlock).
Changes
- API key responses now include an
ip_allow_listfield. The list, get, create, and update API-key endpoints always return the key’s allow list (an empty array when the key has no restriction). - Changing a key’s IP allow list in the dashboard now requires confirming it’s you. Saving an allow-list change prompts for a current two-factor code (or a recovery code) if you have two-factor enabled, otherwise your account password; the confirmation is remembered for five minutes. Dashboard management works from any IP and is never blocked by a key’s own allow list.
- Security alert email for allow-list changes. When an API key’s IP allow list is created or changed, account owners and administrators receive an email noting that a security setting on the named key changed. The email never contains the key’s secret or the list contents.
- The v2 domain API now accepts and returns a
dkim_selectorfield. Create and update accept an optional per-domain selector (Platform Partner accounts only; a trimmed-empty value clears it and reverts to the default), and the domain response reports the configured override (null when none is set). The custom tracking, return-path, subscription, and media subdomain fields are now documented as applying to both self-managed and managed DNS. - Every v2 account response now includes a
parent_account_idfield, so you can tell whether an account is a sub-account and which parent it belongs to. - The v1 send API now rejects a request with neither a text body nor an HTML body. Both the transactional send and the send-with-substitutions endpoint return a validation error when both body fields are omitted; at least one must be provided.
- Suspended accounts can no longer manage sending domains To prevent abuse, while an account is suspended, adding, editing, deleting, or re-checking DNS for a domain is blocked with a message to contact support.
- The billing page now shows emails sent “in the current billing cycle” for customers on a paid subscription, instead of “in the past 30 days.”
- Custom Subdomains on Self-Managed DNS: Domains that use your own self-published DNS can now customize their tracking, return-path, subscription, and media subdomains, matching what was already available for managed-DNS domains. Each subdomain is editable until its DNS record is verified, then locks.
- Golang SDK: The AhaSend Go SDK now supports Sub-Account features and API key IP allow lists.
- CLI: The AhaSend CLI now supports the Sub-Account management features.
Improvements
- Safer API retries. Retrying a request while the original is still processing now returns a clear “conflict” response with retry guidance, and reusing the same idempotency key on a different endpoint, resource, or payload is rejected instead of replaying the wrong response — so retries no longer risk creating duplicate resources.
- Self-lockout protection on API-key allow lists. If you use a key to set its own IP allow list to a value that excludes the IP you’re calling from, the change is rejected and nothing is saved. You can still change it from the dashboard, from an already-allowed IP, or with a different key.
- Sub-account and API-key updates reject empty or no-change requests with a clear error and leave omitted fields untouched; the website field is optional when updating a sub-account.
Bug Fixes
- Fixed legacy plan customers being unable to upgrade to the Pro or Max plan; these upgrades now complete correctly.
- Fixed plan upgrades failing for cards that require extra verification; the upgrade now goes through against your saved card without a failed authentication prompt.
- Fixed a legacy-plan upgrade being blocked for up to 24 hours after you updated your payment method; retrying with a new card now works immediately.
- Fixed backward (“previous page”) cursor pagination on v2 list endpoints, which could jump back to the first page instead of the page immediately before your cursor.
- Fixed the profile account switcher so deleted accounts no longer appear and can no longer break the page.
New Features
- Duplicate a Campaign: You can now duplicate an existing marketing campaign in one action. The copy includes the design, sender settings, subject, preview text, tags, and recipient lists, and opens as a new editable draft — delivery history and statistics are not carried over.
- Automatic Tax on Subscriptions: New paid subscriptions now have applicable sales tax or VAT automatically calculated and added to your invoices based on your billing location.
Changes
- Transactional sends are limited to 100 recipients per request. The v1 send API now enforces a maximum of 100 recipients per call and rejects empty recipient lists with a validation error, matching the documented v2 limit.
- Each recipient gets a unique Message-ID. When you send to multiple recipients via the v1 send API without supplying your own Message-ID, every recipient now receives an individually generated, unique Message-ID instead of a shared one, making each message separately trackable. Supplying your own Message-ID works as before.
- Higher domain limits for enterprise and reseller accounts. Enterprise and Platform Partner accounts can now add up to 10,000 sending domains.
Improvements
- Cost estimates now reflect your subscription’s currency. The estimated-amount figure on the billing page is calculated using EUR or GBP pricing for EUR/GBP subscribers, instead of dollar-based rates.
- Fewer false-positive account suspensions. Automated bounce-rate protection now measures your actual bounce rate over the trailing 30 days and only acts once you’ve accumulated a meaningful number of bounces, so low-volume accounts are no longer suspended over a handful of bounces. Reviewed accounts that are reinstated are judged only on their activity going forward.
- Clearer error when adding a duplicate contact. Adding a contact whose email already exists now shows a specific “a contact with this email address already exists” message instead of a generic failure.
- Better partial-failure reporting on multi-recipient sends. When some recipients in a send fail, the API response now clearly reports which recipients failed and why, rather than failing the entire request opaquely.
Bug Fixes
- Fixed credit purchases that could silently fail to complete; credit top-ups are now provisioned reliably.
- Fixed plan upgrades occasionally resolving to the wrong plan when an invoice contained proration credits.
- Fixed the dashboard billing page showing a contradictory “your subscription is about to be cancelled” banner for customers who had scheduled a cancellation and then started a new subscription.
- Fixed the billing and credits pages failing to load for brand-new subscriptions that had not yet produced their first invoice; usage, billing dates, and estimated charges now display correctly in that window.
- Fixed the credits history failing to render entries that had no linked invoice; such entries now show “N/A” and a support contact link.
- Fixed the dashboard notice about an upcoming scheduled plan change to show the correct current and target plans and the right wording (for example, “downgrade” only when it actually is one), and to no longer appear when there is no real pending change. Plan changes made through the Stripe billing portal now reflect correctly on your account.
- Fixed unpaid checkouts falling through without an error message; if a checkout completes without a successful payment, you’ll now see a prompt to retry with a different payment method.
- Fixed updated checkout links for purchasing additional credit packs.
- Improved the contrast of the status indicators on the campaign edit page.
New Features
- Multi-Currency Billing: New paid subscriptions are now billed in EUR, GBP, or USD depending on your location. The same applies when upgrading or switching between Pro and Max plans. Your default currency is captured when your account is verified, and prices on the onboarding and dashboard billing pages — both plan base prices and per-1k-email overage rates — are now shown in your local currency with the correct symbol (€9 / £8 / $10 for the Pro plan, etc.). You can override the auto-detected currency with a
currencycookie. - Custom Subdomains on Domain Creation: The API v2 create-domain endpoint now accepts
tracking_subdomain,return_path_subdomain,subscription_subdomain,media_subdomain, anddkim_rotation_interval_daysso domains can be set up with custom subdomains in a single API call. - Subdomains lock after DNS verification. Once a sending-domain subdomain (tracking, return-path, subscription, media) is verified in DNS, it is locked: any change requires contacting support, and the update domain API returns a clear 400 error indicating which subdomain is locked.
- API v2 Domain Management: API clients can now set and update domain settings and trigger DNS re-checks, including custom tracking, return-path, subscription, media, and DKIM rotation settings for managed DNS domains.
- BYODKIM Compatibility: Platform Partner accounts can now create domains with 1024-bit BYODKIM keys when needed for legacy compatibility. 2048-bit keys remain recommended.
- Work Email Required at Signup: To reduce abuse, new account sign-ups must now use a work email address. Free, personal, and temporary email providers are no longer accepted at registration.
Changes
- Email addresses are normalized on messages and suppressions (lowercased and trimmed of surrounding whitespace) on write and on lookup, making suppression checks robust to casing and whitespace variations.
- API v2 message scheduling validation is stricter: Create Message API scheduled expiration must be in the future and within the documented limit.
Improvements
- Better API v2 error responses.
403 Forbidden(not401) is returned for insufficient scopes;400(not404/412) for invalid sender domains or DNS-invalid domains on send; clearer errors for invalid suppression domains, invalid webhook boolean filters, and message-status enum mismatches. - Idempotent create endpoints document the
422response that is returned when an idempotency key is reused with a different payload. - API v2 domain-scoped routes, webhooks, SMTP credentials, messages, and statistics now handle scoped access more consistently.
- Account updates return the updated account, not the pre-update copy. The account response no longer leaks internal fields such as suspension status, monthly credit, or contact attributes; it now includes
owner_id. - Account billing pages now keep customer currency consistent when viewing existing subscriptions or switching plans.
- Plan cards now show localized overage prices instead of hardcoded USD amounts.
Bug fixes
- Domain names are normalized everywhere. Domain names submitted to the API, the dashboard, or as part of an API key scope are now lowercased, trimmed, and stripped of any trailing dot, so equivalent names are no longer treated as different.
- Dashboard links in campaign review, DNS, route, and webhook notification emails now point directly to the correct pages.
- Fixed manually resumed campaigns getting sent back through the normal evaluation gate after an admin resumed them.
- Fixed message cancellation requiring
messages:sendinstead ofmessages:cancelpermission. - Fixed message send results being assembled without synchronization, which could occasionally drop or duplicate per-recipient entries.
- Fixed webhook list
enabledandon_*boolean query parameters being silently ignored when given non-boolean values.
New Features
- Managed DNS & DKIM Rotation Setup: Sending-domain setup now supports our new native managed DNS flow. Managed domains get two DKIM CNAME records: one required for sending and a second recommended record that lets AhaSend rotate DKIM keys later without asking for another DNS change.
- Contact Detail Pages: You can now view and edit individual contacts directly from the dashboard, including custom attributes, list memberships, engagement statistics, campaign delivery history, and activity logs. Drill-down pages for opens, clicks, and campaigns are available for detailed analysis.
- Message Tag Filtering: Messages can now be filtered by tags in both the API and the dashboard, making it easier to find specific transactional messages.
- Email Proxy Detection: Open and click tracking now correctly identifies email proxies from Gmail, Yahoo, Apple Mail Privacy Protection, Superhuman, and Fastmail, giving you more accurate engagement metrics. Apple MPP detection uses IP range verification rather than user agent matching alone.
- Delivery Analytics: Messages now track egress pool, egress source, and mailbox provider (Gmail, Yahoo, Outlook, etc.), with pre-aggregated statistics tables for future per-provider delivery reporting.
Changes
- Contact imports now update existing contacts’ names and attributes instead of silently skipping duplicates.
- Default message data retention for new accounts is now 7 days (previously 30).
- Campaign “from” addresses are now stored at schedule time, so campaign reports remain accurate even if the sending domain is later removed.
- API v2 list API keys endpoint now returns both send-only and full-access keys.
- The onboarding DNS setup page has a wider layout that is easier to read on all screen sizes.
- Invoices issued prior to March 15, 2026 are noted as unavailable in the dashboard billing page, with a link to support for older invoices.
Improvements
- DNS validation now tries multiple fallback nameservers (Cloudflare, Level3, Google, OpenDNS) before marking a domain as broken, reducing false alarms from temporary resolver issues.
- DNS validation now follows CNAME chains, so domains using intermediate CNAME records (e.g., through a CDN or DNS proxy) validate correctly.
- DMARC checking recovers more gracefully from transient DNS failures, preserving the previously known state instead of marking the domain invalid.
- The dashboard adapts better to light and dark color modes.
- Checkout and payment form initialization is more reliable.
- Zero message-data retention is available within the plan limits and needs AhaSend approval. See retention settings.
Bug Fixes
- Fixed character encoding corruption (mojibake) in campaign emails when HTML entities such as non-breaking spaces were converted to UTF-8 by the tracking system.
- Fixed single-part campaign emails not receiving click or open tracking.
- Fixed API v2 send-only keys not appearing in the list API keys response.
- Fixed domain deletion not cleaning up associated API key scopes.
- Fixed text selection being unreadable in code blocks on the dashboard.
- Fixed checkout validation requiring company name and VAT ID together for Singapore customers, where VAT is not applicable.
New Features
- Campaign Content Spam Check: Campaign validation now includes an AI-based content review step using our own in-house, self-hosted, fine-tuned LLM model in addition to recipient-list checks. This helps catch campaigns that look like spam, phishing, or cold outreach before they are sent.
- Campaign Review Notifications: You now get email notifications when a campaign review is pending, approved, or rejected.
- Campaign Review Improvements: Manual campaign approvals are more flexible. Approved campaigns can either continue as scheduled or be returned to draft, and reviewed campaigns can be approved with bounce-rate evaluation skipped when appropriate.
Changes
- Campaign validation now gives clearer review outcomes, with more specific reasons when a campaign is held for manual review.
- Campaign reports are easier to use on smaller screens thanks to improved responsive tables.
- Campaign report charts render more reliably.
- The admin side of campaign and domain review tooling was expanded significantly, which should lead to faster and more consistent review handling by our support team.
Improvements
- Campaign sending is more reliable when subscriber status changes after scheduling. Recipients who become invalid or disabled before send time are now excluded automatically instead of causing avoidable failures.
- Campaign scheduling and initialization logic was hardened to reduce the chance of duplicate or inconsistent background jobs.
- Domain verification DNS checks now recover more gracefully from temporary DNS issues, including retrying previously valid domains before marking them broken.
- Domain rechecks now happen more intelligently for domains that were valid in the past but later became unhealthy.
- Domain risk analysis is more reliable thanks to improved domain-registration lookup logic and better handling of rate limits and timeouts.
- Message IDs generated bySend Message APIv2, Send Conversational Message APIv2 and Create Message APIv1 are now generated using UUIDv7, improving consistency, ordering and performance for generated IDs.
Bug Fixes
- Fixed API v2 conversational sending not properly respecting API-key sandbox mode.
- Fixed API v2 GetMessage returning
500errors in cases where404was correct. - Fixed HTML message previews in the dashboard so inline images render correctly.
- Fixed campaign validation sampling to avoid already known invalid subscribers.
- Fixed cleanup of expired messages so deletion runs more consistently and starts processing immediately.
- Fixed onboarding and account settings validation to show clearer max-length errors.
- Fixed checkout/account details validation so company name and VAT can be entered independently.
- Improved Stripe webhook handling to prevent errors in some edge cases.
Security
- Improved session security with automatic session refresh after login and password reset.
New Features
- Contact List Validation: Automatically validate your contact lists before sending campaigns. The system checks email syntax, MX records, and uses external verification services to identify invalid or risky addresses. Campaigns with high bounce risk are flagged for review before sending.
- Campaign Cancellation & Rescheduling: Cancel scheduled campaigns or validation in progress, and reschedule campaigns with a new send time.
- Campaigns now send emails faster with concurrent delivery.
- Added delete campaign option with confirmation on the campaign edit page.
- Email notifications for campaign reviews: get notified when your campaign is pending review, approved, or requires changes.
Changes
- Dashboard charts now include campaign email statistics alongside transactional emails.
- “Deferrals” renamed to “Deferred Attempts” on the dashboard for clarity.
- Recent messages section now labeled “Recent transactional messages” to distinguish from campaigns.
- Send Message API now automatically detects template format based on whether substitutions are provided.
Improvements
- Suppression rate calculations now include campaign recipients for more accurate metrics.
- Campaign report page redesigned with separate views for campaigns being validated, scheduled, or paused.
- Many improvements and bugfixes for the Email Template Designer: fixed inline images breaking on edits, added undo/redo buttons, fixed color picker positioning, and resolved image resize issues.
Bug Fixes
- Fixed API sandbox mode for Send Conversational Message API not being respected when set on credentials.
- Fixed Send Message API incorrectly changing custom email header names to lowercase.
- Fixed inability to switch API keys from sandbox mode back to production mode.
- Fixed credit usage notifications not being sent correctly.
- Fixed webhook and S3 failure notifications not being sent correctly.
- Fixed contact and contact attribute delete buttons not working.
- Fixed logo not displaying correctly in Email Template Designer.
- Fixed domain validation issues during account onboarding.
- Fixed list segment filters not working correctly for numeric and yes/no fields.
- Fixed campaign pause notifications showing incorrect complaint rate.
- Fixed campaigns occasionally getting stuck during scheduling or validation.
- Fixed dropdown menu visibility on contacts page.
- Fixed redirect loop for users with 2FA enabled who haven’t completed onboarding (only affected users who requested their accounts to be deleted without deleting their user profile).
- Improvements and bugfixes for the Workflow Engine and Campaign Management features. Request early access.
- Improved dashboard reporting performance.
- Added support for Bring Your Own DKIM keys to Create Domain API (currently only for Platform Partner accounts).
- Added support for domain validation without explicit SPF if Return-Path is configured (currently only for Platform Partner accounts).
- Improved error reporting for invalid pagination cursors in API v2.
- Improved S3 retention performance and reliability.
- Improved reliability of Usage Records and Billing Meters for billing via Stripe.
- Improved speed, reliability and scalability of MTA configuration subsystem, resulting in faster onboarding for accounts, domains and SMTP credentials.
- Fixed corner case issues with adding tracking links to messages.
- Improved spam and phishing monitoring and detection systems.
- Improved security measures in code and infrastructure.
- Multiple minor improvements and bugfixes.
- Added Workflow Engine: currently in private beta and being internally used and tested for AhaSend’s own user-onboarding campaigns.
- S3 retention: Create separate files for each delivery attempt in the S3 bucket, allowing customers to use write-only credentials for archiving and using built-in S3 retention policies.
- Added support for legacy plan customers to switch to new pricing plans.
- Added support for upgrading and downgrading between Pro and Max plans.
- Improved Out-of-Band (OOB) NDR notification handling for bounced outbound messages.
- Added automatic notifications for monthly quotas for Free accounts.
- MX records are no longer shown for root domains (e.g.
example.com) in the dashboard to prevent confusion and potential DNS issues. - Improved account, domain, and credential synchronization speed between the backend and MTA servers.
- Upgraded to Stripe API to
2025-03-31.basil, adding support for improved usage-based pricing (and soon adaptive pricing with local currency support). - Improved DSN report delivery.
- Improved user experience for searching and deleting suppressions in the dashboard.
- Improved spam and phishing monitoring and detection systems.
- Improved security measures in code and infrastructure.
- Multiple bug fixes, improvements and optimizations.
- New marketing website with a completely new design, dark mode, better performance, and improved content
- Implemented new pricing plans; existing customers remain on legacy pricing but can switch anytime
- Added sorting feature to the Messages page in the dashboard
- Improved IP management with better warm-up and IP pool controls
- Improved performance for API v1 and v2
- Fixed dashboard chart issues on Safari
- Improved reliability of webhook and routing uptime monitoring to prevent false-positive downtime reports on the status page
- Fixed dashboard dark mode display issues
- Fixed content handling bug in v2 Get Message API
- Add Dark Mode to the dashboard
- Add Service Status Page
- Add Get Message API
- Improve
Content-TypeandContent-Dispositionhandling for attachments in Create Message API - Add optional global suppression for large volume senders
- Fix issues with false positives in DMARC verification in some corner cases
- Fix DNS verification failures due to large TXT records: Retry truncated TXT lookups over TCP
- Improve and optimize automatic suppression system based on bounce classifications.
- Improve handling of ICS attachments in Create Message API
- Add rate limits for failed login attempts.
- Improve automatic monitoring and suspension system to prevent abuse and false positives.
- Add new endpoint for sending Conversation Messages
- Add support for generating and sending RFC 3464 DSN reports.
- Introduce AhaSend API v2
- Introduce AhaSend Go SDK
- Introduce AhaSend CLI
- Update documentation site
- Add option to group route requests by Message-ID header
- Deploy separate infrastructure and MTA for campaigns
- Campaign management feature (to be released - Request early access)
- Contact and list management
- Campaign creation forms
- Grapesjs-based campaign template editor (to be released)
- Tiptap-based campaign template editor (to be released)
- Campaign reports (to be released)
- Improve DKIM-key check accuracy
- Improve DNS lookup performance
- Add PKCE support for OIDC Single Sign On
- Add option for account-wide 2FA enforcement
- Add support for Domain-Scoped SMTP Credentials
- Add support for Domain-scoped API Keys
- Add support for Sandbox SMTP Credentials
- Add support for Sandbox API Keys
- Add special headers to enable sandbox mode for individual messages
- Add support for multi-domain OIDC SSO
- Incorporate AhaSend’s SPF record into the existing SPF record for customer domain when possible
- Add OpenID Connect SSO
- Add support for 2FA
- Improve reseller whitelabeling options
- Improve DKIM key check accuracy
- Improve dashboard reports and charts
- Improve webhook and routes performance and request throughput.
- Improve dashboard reports and charts
- Improve system performance and email throughput

