{"id":2709,"date":"2019-03-26T21:32:24","date_gmt":"2019-03-26T21:32:24","guid":{"rendered":"https:\/\/developer.microsoft.com\/en-us\/office\/blogs\/?p=2709"},"modified":"2019-03-26T21:32:24","modified_gmt":"2019-03-26T21:32:24","slug":"add-rich-previews-to-messages-using-link-unfurling","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/add-rich-previews-to-messages-using-link-unfurling\/","title":{"rendered":"Add rich previews to messages using Link unfurling"},"content":{"rendered":"<p>One of the most common ways to share content in Microsoft Teams is through links, be it for an update for that task you\u2019re working on or for sharing the latest Game of Thrones trailer. For any publicly accessible link Teams already shows a preview of the link including information like an image, title and a description.<\/p>\n<p>But starting today, we\u2019re allowing apps to customize these previews for domains that it\u2019s interested in. Apps can register a message handler for domains that they want to serve, and in doing so will get a queryLink event every time a link of that domain is entered in the compose box.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-2710\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevOpsLink.png\" alt=\"\" width=\"782\" height=\"230\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevOpsLink.png 782w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevOpsLink-300x88.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevOpsLink-768x226.png 768w\" sizes=\"(max-width: 782px) 100vw, 782px\" \/><\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-2711\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YouTubeLink.png\" alt=\"\" width=\"764\" height=\"191\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YouTubeLink.png 764w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YouTubeLink-300x75.png 300w\" sizes=\"(max-width: 764px) 100vw, 764px\" \/><\/p>\n<p>By default the content is shown in the standard URL preview format supported in Teams, but users have the option to expand to the full card if they wish to do so.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-2712\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YoutubeExpanded.png\" alt=\"\" width=\"774\" height=\"513\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YoutubeExpanded.png 774w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YoutubeExpanded-300x199.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/YoutubeExpanded-768x509.png 768w\" sizes=\"(max-width: 774px) 100vw, 774px\" \/><\/p>\n<h3>How to add it to your app?<\/h3>\n<p>To enable your app to interact with links, you need to add a messageHandlers array to your app manifest, as in the example below: Note that this article assumes that you already have an app with a messaging extension capability, if not please do have a look at our <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/concepts\/messaging-extensions\/messaging-extensions-overview\">documentation on messaging extensions<\/a>.<\/p>\n<blockquote>\n<pre>\"composeExtensions\": [\n{\n    \"botId\": \"abc123456-ab12-ab12-ab12-abcdef123456\",\n    \"messageHandlers\": [\n        {\n            \"type\": \"link\",\n            \"value\": {\n                \"domains\": [\n                    \"*.trackeddomain.com\"\n                ]\n            }\n        }\n    ]\n}\n]<\/pre>\n<\/blockquote>\n<p>Once you&#8217;ve added the domain to listen on to the app manifest, you&#8217;ll need to change your bot code to respond to the below invoke request.<\/p>\n<blockquote>\n<pre>{\n\u00a0 \"type\": \"invoke\",\n\u00a0 \"name\": \"composeExtension\/queryLink\",\n\u00a0 \"value\": {\n\u00a0\u00a0\u00a0 \"url\": \"https:\/\/theurlsubmittedbyyouruser.trackeddomain.com\/id\/1234\"\n\u00a0 }\n}<\/pre>\n<\/blockquote>\n<p>In your app you can use the onAppBasedLinkQuery handler to listen to this event and build a response. Here\u2019s the sample code to building the response to the event in Javascript:<\/p>\n<blockquote>\n<pre>require('dotenv').config();\nimport * as restify from 'restify';\nimport * as builder from 'botbuilder';\nimport * as teamBuilder from 'botbuilder-teams';\n\nclass App {\n\u00a0\u00a0\u00a0 run() {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const server = restify.createServer();\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 let teamChatConnector = new teamBuilder.TeamsChatConnector({\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 appId: process.env.MICROSOFT_APP_ID,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 appPassword: process.env.MICROSOFT_APP_PASSWORD\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Command ID must match what's defined in manifest\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 teamChatConnector.onAppBasedLinkQuery(\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (event: builder.IEvent,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 query: teamBuilder.ComposeExtensionQuery,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 callback: (err: Error, result: teamBuilder.IComposeExtensionResponse, statusCode: number) =&gt; void) =&gt; {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 let response = teamBuilder.ComposeExtensionResponse.result('list').attachments([\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 new builder.ThumbnailCard()\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .title('Test thumbnail card')\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .text('[1234]: Create a cool app ')\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .images([new builder.CardImage().url('https:\/\/bot-framework.azureedge.net\/bot-icons-v1\/bot-framework-default-9.png')])\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .toAttachment()\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ]).toResponse();\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 callback(null, response, 200);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 server.post('\/api\/composeExtension', teamChatConnector.listen());\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 server.listen(process.env.PORT, () =&gt; console.log(`listening to port:` + process.env.PORT));\n\u00a0\u00a0\u00a0 }\n}\nconst app = new App();\napp.run();<\/pre>\n<\/blockquote>\n<p>This will result in the following response being sent to the client, and will be used to build the Link preview.<\/p>\n<blockquote>\n<pre>{\n\u00a0 \"composeExtension\": {\n\u00a0\u00a0\u00a0 \"type\": \"result\",\n\u00a0\u00a0\u00a0 \"attachmentLayout\": \"list\",\n\u00a0\u00a0\u00a0 \"attachments\": [\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"contentType\": \"application\/vnd.microsoft.teams.card.o365connector\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"content\": {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sections\": [\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"activityTitle\": \"[1234]: Create a cool app\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"activityImage\": \"https:\/\/placekitten.com\/200\/200\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 },\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"title\": \"Details\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"facts\": [\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"name\": \"Assigned to:\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"value\": \"[Larry Brown](mailto:larryb@example.com)\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 },\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"name\": \"State:\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"value\": \"Active\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ]\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ]\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 },\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"preview\": {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"contentType\": \"application\/vnd.microsoft.card.thumbnail\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"content\": {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"title\": \"1234: Create a cool app\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"images\": [\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"url\": \"https:\/\/placekitten.com\/200\/200\"\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0]\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0 }\n}<\/pre>\n<\/blockquote>\n<p>Note that if your app responds with more than one preview for a link only the first one will be considered.<\/p>\n<h3>What if the user needs to sign in or setup the app?<\/h3>\n<p>Just <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/concepts\/messaging-extensions\/search-extensions#authentication\">like in the standard messaging extension response<\/a>, responses for auth and setup are both supported. On receiving these, the Teams client will show an inline message to the user asking them to complete the sign in or setup.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-2713\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevopsSetup.png\" alt=\"\" width=\"1432\" height=\"282\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevopsSetup.png 1432w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevopsSetup-300x59.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevopsSetup-1024x202.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2019\/03\/AzureDevopsSetup-768x151.png 768w\" sizes=\"(max-width: 1432px) 100vw, 1432px\" \/><\/p>\n<h3>Try it out yourself!<\/h3>\n<p>App based link previews are in <a href=\"https:\/\/docs.microsoft.com\/en-us\/microsoftteams\/platform\/concepts\/messaging-extensions\/search-extensions#receive-requests-from-links-inserted-into-the-compose-message-box\">Developer preview<\/a> today do try it out and <a href=\"mailto:microsoftteamsdev@microsoft.com\">reach out to us<\/a> for any questions or issues you run into.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Customize link previews for your app using App based link handling with Messaging extensions.<\/p>\n","protected":false},"author":69074,"featured_media":25159,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[128],"tags":[],"class_list":["post-2709","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-teams"],"acf":[],"blog_post_summary":"<p>Customize link previews for your app using App based link handling with Messaging extensions.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/2709","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/69074"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=2709"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/2709\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/25159"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=2709"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=2709"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=2709"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}