Changelog

What shipped, newest first.

RSS

Billing without leaving the page

Start a plan, change it, cancel it or change your card on the billing page itself, with the card fields inside it.

Everything to do with paying now happens on your billing page. Pick a plan and the card fields appear under it; pay, and the plan starts that moment. Change plan and you see what is due now and what the next renewal costs before you confirm. Move down to Free and your plan runs until the period you have paid for closes, with a button to keep it if you change your mind.

The card fields belong to the payment processor and sit inside the page, so your card number never passes through us. The card drawn beside them shows the brand as soon as it is recognised and turns over for the security code, and once saved it shows the card on file: brand, last four digits and expiry.

A new card given because the last payment failed is charged straight away, so a past-due plan comes right without waiting for the next retry. Invoices are unchanged and still listed on the same page.

Emails from Avelto look like Avelto

Sign-in links, alerts, invitations and support replies now share one design, with a button for the one thing to do.

Every email Avelto itself sends now comes from the same template: the sign-in link, the alerts about domain health, bounce and complaint rates, usage and new API keys, team invitations, the notice when you schedule a deletion, and replies from support.

Each one says what happened at the top, keeps the details in a short list rather than a paragraph, and has a single button for the one thing to do about it. The link is also written out underneath, for clients that strip buttons, and the plain-text version says everything the HTML does. Under the card there is one line on why you got it and where to change that.

Nothing about when they are sent has changed, and the alert settings you have already chosen still apply.

Free tier and dedicated IP pricing

Free now covers 5,000 emails a month with no daily cap. Dedicated IPs are priced per plan.

Free now includes 5,000 emails a month. There is still no daily cap on it beyond the one every new account carries for its first week, so a burst on launch day goes out rather than queueing behind a per-day limit. Sending pauses at the monthly limit until the 1st, as before.

Dedicated IP addresses are now priced per plan: £35 a month on Pro, £25 a month on Growth and Enterprise. They are not offered on Free, because a free account never reaches the volume an address needs to build a reputation. The add-on is arranged by talking to us, and the address is warmed by the provider over a few weeks before it carries full volume.

Passkeys and passwords

Two optional ways to sign in beside the emailed link, added from your settings.

The emailed link still signs everyone in and is still the only way to create an account. From today you can add a second way in from Settings, for when you would rather not wait for an email.

A passkey signs you in with your face, fingerprint or device PIN. There is nothing to remember and nothing that can leak, and it works when email does not. Add one from Settings, then use "Sign in with a passkey" on the login page.

A password is there too, for people who prefer one. At least twelve characters, changed or removed only with the current one, and if you forget it the emailed link gets you back in to set another.

Support conversations in the dashboard

Every message you send us is kept on the Support page, with our replies, and you can write back from there.

Writing to us from the dashboard now opens a conversation rather than sending an email into the dark. Each one is kept on the Support page with everything we said back, and you can reply from there or from your inbox. We still email you every reply, so nothing changes if you prefer email.

Behind it, each conversation carries the reply time your plan promises, and our side of the screen shows us when we are late. That is the point: a promise we can measure is one we keep.

Signups are invite-only for now

New addresses join a waitlist while we onboard gradually. Existing accounts sign in as usual.

We are opening the door slowly rather than all at once, because the first week with a new sending provider is the week that decides whether you keep it, and we would rather be there for it.

If you already have an account, nothing changes: the sign-in page works exactly as it did, and so does every invitation you send to a colleague. If you are new, entering your address puts you on a list and we will email you when your spot is ready. That is the whole process — no form to fill in, no call to book.

The queue is worked in the order people joined it. Asking again does not move you up, so there is nothing to gain by re-submitting, and we would rather say that plainly than have anyone wondering.

One detail worth stating, because we thought about it and someone else might wonder: the confirmation page says the same thing whether or not the address already has an account. That is deliberate. Telling you which would turn the sign-in form into a way of finding out who uses us, and the answer belongs in the inbox of whoever owns the address rather than on a page anyone can load.

We have also added a notice bar to the dashboard for the rare days when something is wrong on our side. If you ever see one, it is us telling you before you have to ask.

The sending region is Stockholm

Email now goes out through AWS eu-north-1 in Sweden. Data stays in the EU, as it always has.

Our sending infrastructure runs in AWS eu-north-1, Europe (Stockholm), rather than the Irish region we named previously. Account data, email records and delivery events are stored and processed in the EU exactly as before, so the commitment you signed up to is unchanged — only the country inside the EU that it points at has moved.

Nothing changes for you in the product. You do not need to re-verify a domain, republish a DNS record, or touch your API keys. Sending, webhooks and the dashboard behave identically, and there is no downtime associated with this.

We have updated the places that state the region so they are accurate rather than approximately right: the privacy policy, the DPA's international-transfers clause, the sub-processors table, the security page and the data residency line in your settings all now say Sweden. If you have our DPA on file and your own records name the region, this is the note to attach to them.

If you are self-hosting against our configuration, the AWS_REGION default is now eu-north-1.

Templates, batch send, attachments by URL, and key scopes

Four things the API was missing. Nothing you already send changes.

Four additions, none of which change anything you are already doing.

Templates

Store a subject and body once, send it by name:

JSON
{
  "from": "hello@mail.acme.com",
  "to": "ada@example.com",
  "template_slug": "welcome",
  "variables": { "name": "Ada" }
}

The whole template language is {{name}}, escaped in the HTML part, and {{{name}}} when you are passing markup you built yourself. No loops, no conditionals, no expressions — that is a ceiling rather than a roadmap. If you need a list of order lines, build the markup in your own language, where you can test it, and pass it as one variable.

A variable the template uses and you did not supply is a 422 naming every one that is missing, not an empty string. "Hi ," sent to a hundred thousand people is worse in every way than a send that did not happen.

Every save keeps a version, and restoring writes the old one forward rather than rewinding, so a restore is itself something you can undo. The dashboard has an editor with a live preview, the variables it found in your body, and a button that sends it to you in test mode.

Batch send

POST /v1/emails/batch takes up to 100 messages. Each one is validated, limited and queued exactly as a single send is, so one suppressed address in a nightly run costs you that message and nothing else. The response reports per message, in request order.

Attachments from a URL

An attachment can now be a link instead of base64:

JSON
{ "filename": "invoice.pdf", "url": "https://cdn.acme.com/invoices/123.pdf" }

We fetch it when the send is accepted, so a broken link fails on the call that made it rather than two seconds later. Base64 still works exactly as before.

Key scopes

A key can now be limited to what it needs. The one worth having is send only — the key you put in an application server, which can send and cannot read back what it sent or change anything about the account. The create-key dialog offers it as a preset.

Existing keys are unaffected: every key made before today has every scope, and a new key created without naming any still does. Narrowing is something you choose.

Webhook debugging moved to the dashboard

Three webhook routes left the API before launch, and Replay is now Send again.

Reading back a delivery's stored request and response bodies, replaying a delivery, and sending a synthetic test event have moved out of the API and into the dashboard, where they were always meant to be used.

The reasoning is about what we are promising. Anything on the API is versioned and supported for as long as it is published, so it should earn its place by being something a program does. These three are things a person does once, while getting a handler working. They now live on the webhook page and nowhere else.

Retrying a failed delivery stays on the API, and in the SDK. That one is genuinely a script: when your endpoint comes back up you want to walk your failures and put them back on the queue without opening a browser.

Nothing you can currently call has been taken away. These routes were never in the SDK and were published only briefly, before anyone could depend on them, which is exactly when a cleanup like this should happen.

The dashboard button that was called Replay is now Send again, which describes what it does: it creates a new delivery, so your handler receives the event as if it had just happened. Replay suggested watching a recording of something that already went past, and the two operations are easy enough to confuse without the name working against you.

Both are worth keeping, because they answer different questions. Retry re-attempts a delivery that failed and keeps its attempt count, so the history reads as one event that eventually got through; reach for it when the failure was transient. Send again produces a clean new delivery and leaves the original untouched; reach for it when you have fixed your handler and want to watch the new code receive a past event. Retry is recovery, send again is testing.

Deploys you will not notice

Updates now ship without dropped requests, failed sends or duplicate email.

Shipping an update used to mean a short window where a request could fail. It no longer does. The API and the web app each run two copies, replaced one at a time, and a new copy only takes traffic once it reports healthy.

A copy being retired stops accepting new connections and finishes the requests already in flight before it exits. The background sender does the same with the jobs it is running, and anything left waiting is picked up after the restart.

Sending is also protected end to end. Each email is claimed before it is handed over and carries a token that identifies it, so an interrupted send is completed once, never twice.

Docs that machines can read

Read the API reference and every docs page as OpenAPI, markdown or a single text file.

Anything you can read on the docs site, a script or an AI assistant can read too. The full API description is published as OpenAPI 3.1 at /openapi.json and /openapi.yaml, generated from the same definitions the API validates against, so it cannot drift from what actually runs.

Every docs page, the home page and the pricing page also answer in plain markdown. Add .md to the path or ask for text/markdown and you get the page without any of the layout around it. /docs/llms.txt is the whole of the documentation concatenated in order, and /llms.txt explains what is available and links to it.

There is also a published skill file telling an assistant how to use the API correctly.

MCP server

Let an AI assistant send email and check delivery through your Avelto account.

@avelto/mcp connects an MCP client to your account. The assistant gets seven tools: send an email, fetch one by id, list emails, list and add sending domains, check a domain's verification status, and read your suppression list.

It runs over standard input and output, so there is nothing to host and nothing to deploy. One environment variable, your API key, is all the configuration it needs. Setup instructions for Claude Desktop, Claude Code and Cursor are in the docs. Errors come back with their code intact, so an assistant can tell a rate limit from a plan limit and act accordingly.

Start with a test key. Test sends run the full pipeline but never reach an inbox.

shell
claude mcp add avelto -e AVELTO_API_KEY=av_test_... -- npx -y @avelto/mcp

Pricing ladder

Pick the volume step that matches what you send, with published prices and overage.

Pricing is now a ladder rather than a handful of tiers. Free covers 10,000 emails a month at no cost. Paid steps run from Pro 50k at £12 a month up to Growth 2.5M at £450, and the price per additional 1,000 emails falls as you climb, from £0.60 down to £0.30. Prices are in pounds and exclude VAT.

Above three million emails a month, Enterprise adds custom volume and overage, ninety-day retention, a 99.9% uptime commitment, a named contact and EU data residency. There is a contact form for that.

The pricing page carries a volume selector and a cost-at-scale table so you can see the effective rate per 1,000 at every step. Test-mode sends are never metered.

Dashboard

See and manage everything you send from the browser, without writing a request.

The dashboard is open. Sign in with a link sent to your email, no password to remember. The overview shows sends today, over seven days and over thirty days, along with your delivery, bounce and complaint rates.

The email log is filterable by status, mode and tag, and opening a message shows its headers, a timeline of every event, and a preview of the HTML rendered in isolation so it cannot affect the page. Add a domain and the DNS records appear with copy buttons while the status polls itself. You can also create and revoke API keys, add webhook endpoints and review their deliveries, and remove addresses from your suppression list. A banner makes it obvious when you are looking at test data.

Webhooks

Get signed delivery, bounce and complaint events posted to your own endpoint.

Point Avelto at an endpoint and every email event arrives as a JSON POST. Choose the event types you want or take them all. The signing secret is shown once when you create the endpoint.

Each request carries an Avelto-Signature header holding a timestamp and an HMAC-SHA256 of the raw body. Verify before you parse, and reject anything more than five minutes old. Failed deliveries are retried up to eight times with backoff, and every retry keeps the same event id so you can de-duplicate safely. You can list an endpoint's deliveries, see why one failed, and retry it by hand. Test-mode sends produce real webhooks too, marked as test.

text
Avelto-Signature: t=1758103929,v1=5f1c2a9b7e3d…

Test mode and the sandbox

Build and test the whole sending flow before you verify a domain or touch a real inbox.

Keys now come in two modes. A key beginning av_test_ runs everything a live key does, including validation, sender rules, suppression checks, the event log and your webhooks. The only step that does not happen is the send itself. About two seconds later the email is marked sent and then delivered, and it appears in your logs alongside live traffic. Test sends are free and never count towards your monthly volume.

Every account can also send from the sandbox domain before verifying one of its own, delivering to the account owner's verified address. Three simulator recipients force a fixed outcome, delivered, bounced or complained, so you can exercise each branch of your webhook handler. They never add anything to your suppression list.

Node SDK

Send email from Node with a typed client that handles retries and idempotency for you.

The @avelto/sdk package is the official Node client. It needs Node 20 or newer, works with both ESM and CommonJS, is fully typed, and has no runtime dependencies. It covers emails, domains, webhooks and suppressions.

Two things happen without you asking. Every send carries an idempotency key, a fresh one per call unless you pass your own, and failed requests are retried up to three times with exponential backoff and jitter, honouring Retry-After on rate limits. Requests that could have been processed but are not safe to repeat are left alone. The package also exports verifyWebhookSignature so your handler can check an incoming event in one line.

TypeScript
import { Avelto } from "@avelto/sdk";

const avelto = new Avelto(process.env.AVELTO_API_KEY);

const { id } = await avelto.emails.send({
  from: "hello@mail.acme.com",
  to: "jane@example.com",
  subject: "Welcome to Acme",
  html: "<p>Thanks for signing up.</p>",
});

The Avelto API

Verify a domain, create an API key and send email with a single request.

Avelto is live. Verify a sending domain, create an API key, and post one JSON body to send an email. Every send returns an id you can look up, with a status and a full event log covering sent, delivered, bounced, complained and failed.

Send an Idempotency-Key header and a retry can never create a second email. Set scheduled_at to hold a message for up to thirty days, and cancel it any time before it goes. Hard bounces and complaints are added to your suppression list automatically, so you never send to an address that already rejected you.

shell
curl -X POST https://api.avelto.dev/v1/emails \
  -H "Authorization: Bearer av_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "hello@mail.acme.com",
    "to": "jane@example.com",
    "subject": "Welcome to Acme",
    "html": "<p>Thanks for signing up.</p>"
  }'