How to: Use Microsoft Graph to import message data into Microsoft Teams and preserve timestamp history, message order, from user etc.
Introduction
One big challenge with migrating to Microsoft Teams for messaging is migration of existing data. For instance, an organisation might be moving from Slack to Teams but have 5+ years worth of message history in Slack. Moving over that history has previously been hard: there hasn’t been a way of adding messages with a specific historic date/time, or from a specific user. There are third-party solutions which ingest the data and then present it inside Teams in a tab: but it’s not part of the main conversation thread, and can lead to a disjointed experience.
However, that’s all changing. A new set of APIs have come to Microsoft Graph which make importing third-party platform messages into Microsoft Teams possible. These APIs are in beta today, meaning that they shouldn’t be used in production. However, organisations can test out migration paths using these APIs in readiness for their move to production.
This also has some pretty big implications for being able to restore from a backup. Today, there isn’t a very clear backup/restore story because there hasn’t been the ability to replay messages back into a Team following an accidental deletion or other catastrophic loss of data. This potentially changes that.
Reference: most of the content from this blog post has been taken from the Microsoft Docs page: Import third-party platform messages to Teams using Microsoft Graph
This feature is in public preview. This means you shouldn’t use it in production. In addition, I have found in testing that this functionality may not yet be available to all tenants. If you see errors about functionality not being supported, you might need to wait until this has happened.
Concept
One key requirement when migrating is to preserve the timestamp and sender of each message. This means posting on behalf of other users, and also manipulating the message creation timestamp on messages. However, you can’t have applications just posting messages into teams all over the place, disrespecting time and space and ownership. There has to be some order.
The solution to this problem is the idea of Migration Mode – a special state which a Team and Channel can be placed into. Teams/Channels in Migration Mode aren’t accessible to most users, but during Migration Mode an application can programatically insert messages. Once the import is completed, the Team (and all its Channels) are taken out of Migration Mode and becomes a regular Team again. Users can be added as usual, but each Channel will be populated with any imported messages.
This is an important point. This is way more powerful than existing import solutions. Once it’s taken out of Migration Mode, the Team is a regular Microsoft Teams team with messages, conversations, replies exactly as if they originally happened in Teams. Except…they didn’t, they were imported.
What can be imported / specified?
The following things can be imported:
- Team and channel messages
- Created time of the original message
- Inline images as part of the message
- Links to existing files in SPO/OneDrive
- Messages with rich text
- Message reply chain
It’s worth pointing out the things that, today, cannot be imported. These are currently not possible – this might change over time, but right now, you can’t import:
- 1:1 and group chat messages
- Private channels
- At mentions
- Reactions
- Videos
- Announcements
- Code snippets
- Adaptive cards
- Stickers
- Emojis
- Quotes
- Cross posts between channels
Good to know before you start…
- You can actually import messages and assign them to users who aren’t in Microsoft Teams. This blows my mind slightly (and I haven’t yet tried it) but it seems like there isn’t a really strong referential link between the message and the user. I guess that this is to support users who have since left the organisation or were never moved to Microsoft Teams.
- There is throttling in place – at a fairly leisurely 5 RPS per channel. If you are importing a channel which has had 50 messages per day for the last 5 years, expect to allow around 5 hours for it to complete.
- You can’t make any mistakes! If you import something incorrectly, it seems like there isn’t any support for deleting specific messages – instead you have to delete the entire team and start again.
- You can’t move in and out of Migration Mode at will: it seems to be a one-time thing that is defined at team creation, and once you’ve completed migration, you can’t start it again.
- In order to perform the import steps below, you need to be using an Azure AD Application which has the specific (and new) permission set of Teamwork.Migrate.All. This is an application-only type of application which requires admin consent.
OK. We know what we can do, what we can’t do, and we’re ready. How do we actually go about importing messages into a team… Here’s the 3-step plan:
Step 1 – Create a team in special “Back in Time” mode
You can’t import into an existing Team. Instead, you have to create the team, using the Microsoft Graph POST call to graph.microsoft.com/beta/teams. Crucially, though you also have to specify in the body a @microsoft.graph.teamCreationMode property with the value of migration and a createdDateTime property with a specific date time. You need to do this from an application (not a user) with the Teamwork.Migrate.All permission:
POST https://graph.microsoft.com/beta/teams Content-Type: application/json { "@microsoft.graph.teamCreationMode": "migration", "[email protected]": "https://graph.microsoft.com/beta/teamsTemplates('standard')", "displayName": "My Sample Team", "description": "My Sample Team’s Description", "createdDateTime": "2020-03-14T11:22:17.067Z" }
If you’ve used Microsoft Graph to create Teams before, then you’ll probably have noticed that the strange-looking @microsoft.graph.teamCreationMode attribute is new. This is a “Instance attribute” used to define special behavoirs, in this case setting the team into Migration mode. From the team resource type definition page:
Once the Team has been created, repeat the same thing to create one or more channels within the Team, also with a specific mode and createdDateTime:
POST https://graph.microsoft.com/beta/teams/{id}/channels Content-Type: application/json { "@microsoft.graph.channelCreationMode": "migration", "displayName": "Architecture Discussion", "description": "This channel is where we debate all future architecture plans", "membershipType": "standard", "createdDateTime": "2020-03-14T11:22:17.067Z" }
Now, you have a Team and one or more Channels, which are created but which are in Migration Mode. Regular users can’t access them, but you can now import messages into them.
Step 2 – Import the messages
To import messages, call the Microsoft Graph POST call of graph.microsoft.com/beta/teams/{teamId}/channels/{channelId}/messages as you would to send regular messages, but specifically including a createdDateTime property, and a from property. The createdDateTime value can be in the past, and doesn’t have to be the current date. (It probably shouldn’t be before the createdDateTime of the channel though). The from property is actually an array of type identitySet meaning it should be possible to specify an application, device, or user here. In the example below I’m specifying a user:
POST https://graph.microsoft.com/beta/teams/teamId/channels/channelId/messages { "replyToId": null, "messageType": "message", "createdDateTime": "2019-02-04T19:58:15.511Z", "lastModifiedDateTime": null, "deleted": false, "subject": null, "summary": null, "importance": "normal", "locale": "en-us", "policyViolation": null, "from": { "application": null, "device": null, "conversation": null, "user": { "id": "id-value", "displayName": "Tom Morgan", "userIdentityType": "aadUser" } }, "body": { "contentType": "html", "content": "Hello World" }, "attachments": [], "mentions": [], "reactions": [] }
In this way, you can also handle threaded conversations with replies – you just need to make sure to process the response of each post and capture the id, which you can then use as the replyToId in subsequent posts.
Step 3 – Close the hole in the space-time continuum
Once you’re done importing and re-writing history, it’s time to put things back to normal.
Send a POST request to each channel using graph.microsoft.com/beta/teams/{teamId}/channels/{channelId}/completeMigration and then to the Team using graph.microsoft.com/beta/teams/{teamId}/completeMigration. You don’t need to specify a body. This will complete the migration and take the entire Team (and its channels) out of Migration Mode.
At this point, they will be ‘normal’ Teams. You can add users to them and interact/manage them like any other Team. Users will be able to continue any conversations that were imported, as if they created them originally in Teams.
Conclusion
This is a major milestone for migrating messaging workloads to Microsoft Teams. For the first time, it’s now possible to transition users from another platform to Teams and have them be able to pick up where they left off with all their messages and conversations in the right place. It’s a really great addition to the toolkit, and I hope it helps more people move over to Microsoft Teams.
If I’m honest, this still makes me head hurt a bit thinking about it. It’s quite a ‘behind the curtain’ sort of operation that changes how I think about Teams messages. These aren’t operations for the every-day, but if you need to migrate over messages from third-party systems into Microsoft Teams, then these API calls will give you the power you need to achieve it. Just, make sure you don’t break any other parts of the delicate balance of time and space whilst you’re at it. 🙂