Microsoft announced in September 2026 that Azure Communication Services (ACS) Chat retires on 30 September 2028.
There is already decent coverage of the announcement itself, which services are affected, what the replacement options are, and how to map your dependencies.
This post is about the part I haven't seen enough people talk about, and it happens to be the part with a hard deadline attached:
What happens to your messages?
If your application has been using ACS Chat as its conversation store, this is the migration problem you need to start thinking about now.
The sentence to read twice
Microsoft's retirement and breaking changes guide, which describes itself as the authoritative source, says:
After the retirement date, the supporting data and telemetry for retired and standalone services will be decommissioned.
And, more specifically:
ACS will maintain chat history according to your storage policies until the retirement date of September 30, 2028.
Read that again.
Your chat history is not migrated for you.
It doesn't sit somewhere waiting for you to collect it after the service disappears.
If those conversations matter, support threads, care conversations, customer communications, audit history, anything you may be asked to produce two years from now, they need to leave ACS before ACS Chat does.
There is also a nearer date worth knowing:
23 October 2026: new customers can no longer sign up for the retiring services.
Existing resources continue working through the transition, so if you are already using ACS Chat, that date doesn't lock you out of your existing estate.
It simply closes the door behind you.
The official path and who it is for
Microsoft's stated replacement is the Microsoft Graph Chat APIs, which the retirement guide lists as requiring a Teams license.
For a large set of customers, that is probably the intended migration path.
If your chat is between people who already have Microsoft 365 identities, and you're comfortable with Teams owning the conversation experience and governance model, the official path may fit your workload.
This post isn't an argument against it.
The problem is that not every ACS Chat workload looks like Teams.
And one thing is worth saying explicitly:
Microsoft will not migrate your ACS chat history into Teams for you. Graph does have a message import API, but running it is your project: extracting from ACS, mapping every author to an Entra identity, transforming content, reconciling counts. The tooling exists; the work is yours.
That distinction matters because the difficult part isn't simply putting messages into another API.
It's preserving the meaning of those messages while moving between two different identity and conversation models.
Who it is not for
Microsoft's retirement guide is unusually direct about this.
It says workloads requiring:
- anonymous or application-defined identities
- a white-label embedded client
- consumer-scale chat outside Teams
should evaluate other communication providers or redesign the experience.
If you recognise your product in that description, you're in the group with the most architectural work and the least prescriptive migration guidance.
Let's make those requirements concrete.
Application-defined identities
ACS allows your application to create communication users who aren't people in your Microsoft Entra ID.
Think:
- patients
- customers
- drivers
- marketplace users
- external participants
- application-specific accounts
There is no Entra identity to map them to.
They were never supposed to have one.
White-label embedded chat
Your product, your branding, your auth, your workflow.
"Send them to Teams" isn't an option you can offer.
Consumer-scale chat
You may have thousands or millions of users.
Giving every participant a Microsoft 365 license isn't practical, nor is Teams necessarily the product experience you want to expose.
For these workloads, the destination is an open architectural decision.
The extraction is not.
Whatever you eventually choose, your messages have to come out of ACS first.
And that extraction problem is essentially the same regardless of where you eventually put the data.
Why getting the data out is harder than it sounds
This is the part I want to be useful about.
I know because I did a smaller, unplanned version of this migration last year.
And I got most of it wrong the first time.
Here are the things that caught me.
1. There is no export button
This sounds obvious, but it changes the entire migration strategy.
There isn't a convenient:
Export all my ACS Chat history
API.
There is no bulk export operation that gives you your entire chat estate in one shot.
Instead, you need to walk the ACS REST APIs:
Resource
↓
Identity
↓
Chat Threads
↓
Messages
↓
Participants
And you need to do that thread by thread and page by page.
That means your migration process needs to deal with:
- pagination
- throttling
- retries
- transient failures
- authentication
- checkpointing
- resumability
- partial migrations
This isn't a one-off script you run from your laptop and forget about.
2. ACS identities are scoped to the resource that created them
This is the one that bites hardest.
An ACS identity looks like:
8:acs:<resourceGuid>_<userGuid>
Notice the resource GUID.
It's part of the identity.
That means an ACS identity isn't globally portable.
It is tied to the ACS resource that created it.
And that creates a nasty data-model problem.
Suppose your application has something like:
users
-------------------------
id
name
acs_identity
And your messages reference that ACS identity:
messages
-------------------------
id
thread_id
sender_acs_identity
content
created_at
That feels perfectly reasonable while ACS is your chat platform.
But once the ACS resource disappears, those identities disappear with it.
Your application-level relationship between:
Your User ID
↓
ACS identity
↓
Message
is no longer a durable identity model.
I learned this the hard way
During one resource migration I replayed 7,200 threads. The messages didn't disappear. Something worse happened. Of the messages in those threads, 7,022 displayed the wrong author, every one showing the system user as the sender.
Every replayed thread also rejected replies with:
Forbidden
At the same time, 649 stored ACS identities belonged to the dead resource.
Nobody lost a message.
The important part is why we were able to recover from it.
The original extraction had accidentally preserved the sender IDs and participant lists.
That meant we still had enough information to reconstruct the original authorship and participant relationships.
I had preserved those fields because they happened to be in the extraction.
Not because I had designed the migration around them.
That was luck.
Don't build your migration on luck.
Preserve the sender IDs and participant lists deliberately.
The lesson wasn't that the messages were safe.
The lesson was that the message body alone isn't the history.
The identity relationships around the message are part of the history too.
3. You can only list threads an identity participates in
Another problem is discovering the complete set of conversations.
There isn't an administrative:
listAllChatThreads()
operation that simply gives you everything.
listChatThreads operates in the context of the identity whose token you're using.
That means extraction becomes a discovery problem.
You need to know:
Who can see which threads?
If your system identity was added to every conversation, you're in relatively good shape.
If it wasn't, things get more interesting.
Some threads may only be reachable through individual participants.
And if your application has lost track of those participants, you may have conversations that are difficult, or potentially impossible, to discover through your normal application flow.
This is why inventorying the estate should be the first step, not writing the migration script.
4. Large migrations are long enough to fail
Let's say you have:
- thousands of threads
- tens of thousands of messages
- multiple participants
- API throttling
- network failures
Your migration isn't going to be instantaneous.
And if your extraction script looks like this:
for thread in threads:
download(thread)
you're going to have a bad day.
You need something closer to:
discover
↓
checkpoint
↓
extract
↓
validate
↓
checkpoint
↓
retry failures
↓
resume
At minimum, build for:
Pagination
Never assume one API response contains everything.
Retry with backoff
Expect throttling and transient failures.
Checkpointing
Record what has already been extracted.
Resumability
If the process dies after 4,000 threads, you should restart from approximately 4,000, not from zero.
Idempotency
Running the migration again shouldn't duplicate everything.
A migration tool that can resume safely is considerably more valuable than one that is fast when everything goes perfectly.
Because everything rarely goes perfectly.
5. Timestamps and authorship don't automatically survive a migration
This is another easy trap.
Suppose you extract:
{
"sender": "8:acs:...",
"message": "Hello",
"createdAt": "2026-02-14T10:23:11Z"
}
and then replay that message into another chat system.
The destination system will generally know:
createdAt = NOW()
sender = whoever performed the API request
not:
createdAt = 2026-02-14T10:23:11Z
sender = original participant
If historical fidelity matters, you need to preserve those values explicitly.
For example, your durable message model might look more like:
{
"id": "...",
"conversationId": "...",
"authorId": "...",
"body": "Hello",
"originalCreatedAt": "2026-02-14T10:23:11Z",
"source": "azure-communication-services"
}

The exact schema is up to you.
The important thing is deciding before migration which fields represent historical truth.
Otherwise you can successfully migrate every message and still lose the two attributes that make the conversation understandable:
who said it and when they said it.
A plan that works backwards from the deadline
The temptation is to start with:
"Which chat provider are we moving to?"
I think that's backwards.
Start with the data.
Step 1: Find out what you actually have
Before choosing a destination, build an inventory.
At minimum, determine:
Total threads
Total messages
Total participants
Active identities
Stored identities
Stale identities
Threads per identity
Threads without system identity
Messages per thread
Oldest message
Newest message
You may be surprised by the numbers.
Especially if ACS has been running for several years.
You also want to identify conversations where your application no longer has a usable participant identity.
Those are the conversations you don't want to discover six months before retirement.
Step 2: Get the history into a store you own
This is the most important step.
Don't make your future chat provider your archive.
Create a durable copy of your history in infrastructure that you control.
And don't key that archive primarily around ACS identities.
Use your application's own identifiers:
Your User ID
↓
Conversation ID
↓
Message ID
Keep the ACS identifiers as source metadata where useful, but don't make them the foundation of your long-term identity model.
Preserve the original:
- message ID
- conversation/thread ID
- sender ID
- participant list
- message body
- original timestamp
- message type
- attachments/metadata where applicable
- relevant source identifiers
The exact fields depend on your application and retention requirements.
Once the history is safely in your own datastore, the retirement deadline changes character.
It stops being primarily a data-loss risk.
It becomes an ordinary migration project.
Step 3: Decide where new conversations belong
Only now should you make the destination decision.
Depending on your requirements, that might be:
Microsoft Graph / Teams
If your users and product model fit Microsoft's Teams-based direction.
Another communication provider
If you need:
- application-defined identities
- embedded chat
- white-label UI
- consumer-scale conversations
Your own chat system
If chat is a core product capability and you need complete control over:
- identity
- storage
- UI
- retention
- search
- moderation
- auditability
- integrations
The important thing is that the archive shouldn't depend on which one you eventually choose.
Step 4: Verify
Don't finish when the migration script says:
Migration complete: 100%
Finish when the data has been verified.
At minimum, compare:
ACS Destination/archive
Thread count == Thread count
Message count == Message count
Participant count == Participant count
Author mapping == Author mapping
Original dates == Original dates
And don't only verify totals.
Pick real conversations and inspect them.
For example:
Conversation A
37 messages
4 participants
Conversation B
12 messages
2 participants
Conversation C
184 messages
7 participants
Verify:
- first message
- last message
- authorship
- ordering
- timestamps
- participants
- attachments
- message metadata
A migration that finished isn't necessarily a migration that is correct.
The architecture
The migration is easier to reason about if you separate extraction from destination.
The middle box is deliberately generic.
Threadvault is one implementation of that extraction layer; a script you write yourself is another.
The important architectural boundary is that the extraction layer gets the history into a durable format you control before the destination becomes part of the problem.
So I built a tool for it
I wrote the recovery tooling for my own incident afterwards.
Then I turned it into something reusable, because writing migration tooling under pressure at 2 AM was miserable, and entirely avoidable.
It's called Threadvault.
Threadvault is an open-source CLI for extracting and preserving Azure Communication Services Chat history.
It's:
- Open source
- Apache-2.0 licensed
- Free
- No paid tier
- No account required
And to be completely clear:
Threadvault does not migrate your history into Teams.
It is focused on the extraction and preservation problem.
The goal is much simpler:
Get your data out of ACS while you still can.
Start with doctor
The first command is:
npx threadvault doctor
doctor is deliberately read-only.
It runs in seconds, never reads a message body, audits five failure modes, and gives you a number you can use to understand the state of your ACS Chat environment before attempting a migration.
The point is to answer:
What does my ACS estate actually look like?
before you start moving anything.
One more reason to run it against real infrastructure
I have 215 tests and 79% code coverage.
That wasn't enough.
Running the tool against real infrastructure found ten defects that the test suite didn't catch.
That's not a claim that the tool is perfect.
It's the opposite.
It is a reminder that API behavior, real identity graphs, pagination, stale resources, permissions, and production data tend to expose failure modes that unit tests don't.
That's also why I would strongly recommend running any extraction tooling against your actual ACS estate early, not just against mocked data.
Don't wait until 2028
30 September 2028 sounds far away.
For a production chat estate, it isn't.
The migration isn't just:
ACS → New Chat Provider
It's:
Discover
↓
Inventory
↓
Extract
↓
Preserve identities
↓
Preserve timestamps
↓
Validate
↓
Archive
↓
Choose destination
↓
Migrate active experience
And the first half of that work is independent of whatever your eventual destination becomes.
That's the useful thing about starting early.
You don't have to know today whether you're moving to Teams, another provider, or something you build yourself.
You only need to make sure that your historical conversations don't disappear with the service that currently holds them.
The takeaway
If you're running ACS Chat today, I would ask four questions now:
- How many threads and messages do we actually have?
- Can we reach every conversation through the identities we still control?
- Are our application-level user IDs mapped independently from ACS identities?
- Do we have a verified copy of the history in infrastructure we own?
If the answer to #4 is no, that's probably the work to start with.
The destination can come later.
The data has a deadline.
Resources
- Microsoft ACS Retirement and Breaking Changes Guide
- Threadvault on GitHub
- Threadvault documentation, runbook, and post-mortem
Dates and quotations accurate as of 27 September 2026. Microsoft's retirement guide is the authoritative source and may change.
If you're dealing with an ACS Chat estate and have run into identity, pagination, discovery, or migration problems, I'd be interested in hearing what you've found.
There are probably more edge cases hiding in production systems than any migration guide will mention.

Top comments (0)