Messages exchanged between user and bot can contain media attachments, such as images, video, audio, and files. The Bot Framework SDK supports the task of sending rich messages to the user. To determine the type of rich messages a channel (Facebook, Slack, and so on) supports, consult the channel's documentation for information about limitations.
Note
The Bot Framework JavaScript, C#, and Python SDKs will continue to be supported, however, the Java SDK is being retired with final long-term support ending in November 2023.
Existing bots built with the Java SDK will continue to function.
All of the source code shown in this section is based on the Handling attachments sample.
The Attachments property of the Activity object contains an array of Attachment objects that represent the media attachments and rich cards attached to the message. To add a media attachment to a message, create an Attachment object for the reply activity and set the ContentType, ContentUrl, and Name properties.
To create the reply message, define the text and then set up the attachments. Assigning the attachments to the reply is the same for each attachment type, however the various attachments are set up and defined differently, as seen in the following snippets. The code below is setting up the reply for an inline attachment:
Bots/AttachmentsBot.cs
C#
{
reply = MessageFactory.Text("This is an inline attachment.");
Next, we look at the types of attachments. First is an inline attachment:
Bots/AttachmentsBot.cs
C#
{
var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png");
var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath));
returnnew Attachment
{
Name = @"Resources\architecture-resize.png",
ContentType = "image/png",
ContentUrl = $"data:image/png;base64,{imageData}",
};
}
Then, an uploaded attachment:
Bots/AttachmentsBot.cs
C#
{
if (string.IsNullOrWhiteSpace(serviceUrl))
{
thrownew ArgumentNullException(nameof(serviceUrl));
}
if (string.IsNullOrWhiteSpace(conversationId))
{
thrownew ArgumentNullException(nameof(conversationId));
}
var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png");
var connector = turnContext.TurnState.Get<IConnectorClient>() as ConnectorClient;
var attachments = new Attachments(connector);
var response = await attachments.Client.Conversations.UploadAttachmentAsync(
conversationId,
new AttachmentData
{
Name = @"Resources\architecture-resize.png",
OriginalBase64 = File.ReadAllBytes(imagePath),
Type = "image/png",
},
cancellationToken);
var attachmentUri = attachments.GetAttachmentUri(response.Id);
returnnew Attachment
{
Name = @"Resources\architecture-resize.png",
ContentType = "image/png",
ContentUrl = attachmentUri,
};
}
Lastly, an internet attachment:
Bots/AttachmentsBot.cs
C#
{
// ContentUrl must be HTTPS.returnnew Attachment
{
Name = @"Resources\architecture-resize.png",
ContentType = "image/png",
ContentUrl = "https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png",
};
}
}
To create the reply message, define the text and then set up the attachments. Assigning the attachments to the reply is the same for each attachment type, however the various attachments are set up and defined differently, as seen in the following snippets. The code below is setting up the reply for an inline attachment:
bots/attachmentsBot.js
JavaScript
*/
const firstChar = turnContext.activity.text[0];
if (firstChar === '1') {
To send the user a single piece of content like an image or a video, you can send media in a few different ways. First, as an inline attachment:
Lastly, an internet attachment contained in a URL:
bots/attachmentsBot.js
JavaScript
* Returns an attachment to be sent to the user from a HTTPS URL.
*/
getInternetAttachment() {
// NOTE: The contentUrl must be HTTPS.
return {
name: 'architecture-resize.png',
contentType: 'image/png',
contentUrl: 'https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png'
The source code shown in this section is based on the Handling attachments sample.
The getAttachments() method of the Activity object contains an array of Attachment objects that represent the media attachments and rich cards attached to the message. To add a media attachment to a message, create an Attachment object for the reply activity and set the ContentType, ContentUrl, and Name properties.
To create the reply message, define the text and then set up the attachments. Assigning the attachments to the reply is the same for each attachment type, however the various attachments are set up and defined differently, as seen in the following snippets. The code below is setting up the reply for an inline attachment:
AttachmentsBot.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Next, we look at the types of attachments. First is an inline attachment:
AttachmentsBot.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Then, an uploaded attachment:
AttachmentsBot.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Lastly, an internet attachment:
AttachmentsBot.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
To create the reply message, define the text and then set up the attachments. Assigning the attachments to the reply is the same for each attachment type, however the various attachments are set up and defined differently, as seen in the following snippets.
The code below is setting up the reply for an inline attachment:
bots/attachments_bot.py
Python
reply.text = "This is an inline attachment."
reply.attachments = [self._get_inline_attachment()]
To send the user a single piece of content like an image or a video, you can send media in a few different ways. First, as an inline attachment:
bots/attachments_bot.py
Python
def_get_inline_attachment(self) -> Attachment:"""
Creates an inline attachment sent from the bot to the user using a base64 string.
Using a base64 string to send an attachment will not work on all channels.
Additionally, some channels will only allow certain file types to be sent this way.
For example a .png file may work but a .pdf file may not on some channels.
Please consult the channel documentation for specifics.
:return: Attachment
"""
file_path = os.path.join(os.getcwd(), "resources/architecture-resize.png")
with open(file_path, "rb") as in_file:
base64_image = base64.b64encode(in_file.read()).decode()
return Attachment(
name="architecture-resize.png",
content_type="image/png",
content_url=f"data:image/png;base64,{base64_image}",
)
Then, an uploaded attachment:
bots/attachments_bot.py
Python
asyncdef_get_upload_attachment(self, turn_context: TurnContext) -> Attachment:"""
Creates an "Attachment" to be sent from the bot to the user from an uploaded file.
:param turn_context:
:return: Attachment
"""with open(
os.path.join(os.getcwd(), "resources/architecture-resize.png"), "rb"
) as in_file:
image_data = in_file.read()
connector = await turn_context.adapter.create_connector_client(
turn_context.activity.service_url
)
conversation_id = turn_context.activity.conversation.id
response = await connector.conversations.upload_attachment(
conversation_id,
AttachmentData(
name="architecture-resize.png",
original_base64=image_data,
type="image/png",
),
)
base_uri: str = connector.config.base_url
attachment_uri = (
base_uri
+ (""if base_uri.endswith("/") else"/")
+ f"v3/attachments/{response.id}/views/original"
)
return Attachment(
name="architecture-resize.png",
content_type="image/png",
content_url=attachment_uri,
)
Lastly, an internet attachment contained in a URL:
bots/attachments_bot.py
Python
def_get_internet_attachment(self) -> Attachment:"""
Creates an Attachment to be sent from the bot to the user from a HTTP URL.
:return: Attachment
"""return Attachment(
name="architecture-resize.png",
content_type="image/png",
content_url="https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png",
)
If an attachment is an image, audio, or video, the Connector service will communicate attachment data to the channel in a way that enables the channel to render that attachment within the conversation. If the attachment is a file, the file URL will be rendered as a hyperlink within the conversation.
Send a hero card
Besides simple image or video attachments, you can attach a hero card, which allows you to combine images and buttons in one object, and send them to the user. Markdown is supported for most text fields, but support may vary by channel.
privatestaticasync Task DisplayOptionsAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
// Create a HeroCard with options for the user to interact with the bot.var card = new HeroCard
{
Text = "You can upload an image or select one of the following choices",
Buttons = new List<CardAction>
{
// Note that some channels require different values to be used in order to get buttons to display text.// In this code the emulator is accounted for with the 'title' parameter, but in other channels you may// need to provide a value for other parameters like 'text' or 'displayText'.new CardAction(ActionTypes.ImBack, title: "1. Inline Attachment", value: "1"),
new CardAction(ActionTypes.ImBack, title: "2. Internet Attachment", value: "2"),
new CardAction(ActionTypes.ImBack, title: "3. Uploaded Attachment", value: "3"),
},
};
var reply = MessageFactory.Attachment(card.ToAttachment());
await turnContext.SendActivityAsync(reply, cancellationToken);
To compose a message with a hero card and button, you can attach a HeroCard object to a message.
* @param {Object} turnContext
*/
async displayOptions(turnContext) {
const reply = { type: ActivityTypes.Message };
// Note that some channels require different values to be used in order to get buttons to display text.
// In this code the emulator is accounted for with the 'title' parameter, but in other channels you may
// need to provide a value for other parameters like 'text' or 'displayText'.
const buttons = [
{ type: ActionTypes.ImBack, title: '1. Inline Attachment', value: '1' },
{ type: ActionTypes.ImBack, title: '2. Internet Attachment', value: '2' },
{ type: ActionTypes.ImBack, title: '3. Uploaded Attachment', value: '3' }
];
const card = CardFactory.heroCard('', undefined,
buttons, { text: 'You can upload an image or select one of the following choices.' });
reply.attachments = [card];
To compose a message with a hero card and button, you can attach a HeroCard object to a message.
asyncdef_display_options(self, turn_context: TurnContext):"""
Create a HeroCard with options for the user to interact with the bot.
:param turn_context:
:return:
"""# Note that some channels require different values to be used in order to get buttons to display text.# In this code the emulator is accounted for with the 'title' parameter, but in other channels you may# need to provide a value for other parameters like 'text' or 'displayText'.
card = HeroCard(
text="You can upload an image or select one of the following choices",
buttons=[
CardAction(
type=ActionTypes.im_back, title="1. Inline Attachment", value="1"
),
CardAction(
type=ActionTypes.im_back, title="2. Internet Attachment", value="2"
),
CardAction(
type=ActionTypes.im_back, title="3. Uploaded Attachment", value="3"
),
],
)
Process events within rich cards
To process events within rich cards, use card action objects to specify what should happen when the user selects a button or taps a section of the card. Each card action has a type and value property.
To function correctly, assign an action type to each clickable item on a hero card. This table lists and describes the available action types and what should be in the associated value property.
The messageBack card action has a more generalized meaning than the other card actions. See the Card action section of the Activity schema for more information about the messageBack and other card action types.
Type
Description
Value
call
Initiates a phone call.
Destination for the phone call in this format: tel:123123123123.
downloadFile
Downloads a file.
The URL of the file to download.
imBack
Sends a message to the bot, and posts a visible response in the chat.
Text of the message to send.
messageBack
Represents a text response to be sent via the chat system.
An optional programmatic value to include in generated messages.
openUrl
Opens a URL in the built-in browser.
The URL to open.
playAudio
Plays audio.
The URL of the audio to play.
playVideo
Plays a video.
The URL of video to play.
postBack
Sends a message to the bot, and may not post a visible response in the chat.
Text of the message to send.
showImage
Displays an image.
The URL of the image to display.
signin
Initiates an OAuth sign-in process.
The URL of the OAuth flow to initiate.
Hero card using various event types
The following code shows examples using various rich card events.
For examples of all the available cards, see the Using cards sample.
Cards.cs
C#
publicstatic HeroCard GetHeroCard()
{
var heroCard = new HeroCard
{
Title = "BotFramework Hero Card",
Subtitle = "Microsoft Bot Framework",
Text = "Build and connect intelligent bots to interact with your users naturally wherever they are," +
" from text/sms to Skype, Slack, Office 365 mail and other popular services.",
Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.microsoft.com/bot-framework") },
};
return heroCard;
}
Cards.cs
C#
publicstatic SigninCard GetSigninCard()
{
var signinCard = new SigninCard
{
Text = "BotFramework Sign-in Card",
Buttons = new List<CardAction> { new CardAction(ActionTypes.Signin, "Sign-in", value: "https://login.microsoftonline.com/") },
};
return signinCard;
}
For examples of all the available cards, see the Using cards sample.
createOAuthCard() {
return CardFactory.oauthCard(
'OAuth connection', // Replace with the name of your Azure AD connection'Sign In',
'BotFramework OAuth Card'
);
}
For examples of all the available cards, see the Using cards sample.
Cards.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Cards.java
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
For examples of all the available cards, see the Using cards sample.
defcreate_oauth_card(self) -> Attachment:
card = OAuthCard(
text="BotFramework OAuth Card",
connection_name="OAuth connection", # Replace it with the name of your Azure AD connection.
buttons=[
CardAction(
type=ActionTypes.signin,
title="Sign in",
value="https://example.org/signin",
)
],
)
return CardFactory.oauth_card(card)
Send an Adaptive Card
While you can use the message factory to create a message that contains an attachment (of any sort), an Adaptive Card is one specific type of attachment. Not all channels support Adaptive Cards, and some channels may only partially support Adaptive Cards. For example, if you send an Adaptive Card in Facebook, the buttons won't work while texts and images work well. The message factory is a Bot Framework SDK helper class used to automate creation steps for you.
Adaptive Cards are an open card exchange format enabling developers to exchange UI content in a common and consistent way. However, not all channels support Adaptive Cards.
The Adaptive Cards Designer provides a rich, interactive design-time experience for authoring adaptive cards.
Note
You should test this feature with the channels your bot will use to determine whether those channels support adaptive cards.
The following source code is from the Using cards sample.
Dialogs/MainDialog.cs
First, create the reply and define the attachments as a list.
C#
// Cards are sent as Attachments in the Bot Framework.// So we need to create a list of attachments for the reply activity.var attachments = new List<Attachment>();
// Reply to the activity we received with an activity.var reply = MessageFactory.Attachment(attachments);
Then add the attachments and set the layout type to carousel.
Here we're adding them one at a time, but feel free to manipulate the list to add the cards however you prefer.
C#
// Display a carousel of all the rich card types.
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment());
reply.Attachments.Add(Cards.GetAnimationCard().ToAttachment());
reply.Attachments.Add(Cards.GetAudioCard().ToAttachment());
reply.Attachments.Add(Cards.GetHeroCard().ToAttachment());
reply.Attachments.Add(Cards.GetOAuthCard().ToAttachment());
reply.Attachments.Add(Cards.GetReceiptCard().ToAttachment());
reply.Attachments.Add(Cards.GetSigninCard().ToAttachment());
reply.Attachments.Add(Cards.GetThumbnailCard().ToAttachment());
reply.Attachments.Add(Cards.GetVideoCard().ToAttachment());
Once the attachments are added, you can send the reply just like any other.
C#
// Send the card(s) to the user as an attachment to the activityawait stepContext.Context.SendActivityAsync(reply, cancellationToken);
The following source code is from the Using cards sample.
dialogs/mainDialog.js
Add the attachments and set the layout type to carousel.
Once the attachments are added, you can send the reply just like any other.
The following source code is from the Using cards sample.
MainDialog.java
First, create the reply and define the attachments as a list.
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Then add the attachments and set the layout type to carousel.
Here we're adding them one at a time, but feel free to manipulate the list to add the cards however you prefer.
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
Once the attachments are added, you can send the reply just like any other.
Warning
It looks like the sample you are looking for has moved! Rest assured we are working on resolving this.
The source code shown here is based on Using cards sample.
dialogs/main_dialog.py
First, create the reply and define the attachments as a list.
Python
reply = MessageFactory.list([])
Then add the attachments and set the layout type to carousel.
Here we're adding them one at a time, but feel free to manipulate the list to add the cards however you prefer.
Once the attachments are added, you can send the reply just like any other.
Python
# Send the card(s) to the user as an attachment to the activityawait step_context.context.send_activity(reply)
Code sample for processing Adaptive Card input
The following sample shows one way to use Adaptive Card inputs within a bot dialog class.
It extends the hero cards sample by validating the input received in the text field from the responding client.
You first need to add the text input and button functionality to the existing adaptive card by adding the following code just before the final bracket of adaptiveCard.json, located in the resources folder:
The ID of the text input field is set to "text". When the user selects OK, the message the Adaptive Card generates will have a value property that has a property named text that contains the information the user entered in the text input field of the card.
Our validator uses Newtonsoft.json to first convert this to a JObject,
and then create a trimmed text string for comparison. So add:
C#
using System;
using System.Linq;
using Newtonsoft.Json.Linq;
to MainDialog.cs and install the latest stable NuGet package of Newtonsoft.Json.
In the validator code, we added the logic flow into the code comments.
This ChoiceValidator method is placed into the Using cards sample just after the closed brace public for declaration of MainDialog:
C#
privateasync Task ChoiceValidator(
PromptValidatorContext promptContext,
CancellationToken cancellationToken)
{
// Retrieves Adaptive Card comment text as JObject.// looks for JObject field "text" and converts that input into a trimmed text string.var jobject = promptContext.Context.Activity.Value as JObject;
var jtoken = jobject?["text"];
var text = jtoken?.Value().Trim();
// Logic: 1. if succeeded = true, just return promptContext// 2. if false, see if JObject contained Adaptive Card input.// No = (bad input) return promptContext// Yes = update Value field with JObject text string, return "true".if (!promptContext.Recognized.Succeeded && text != null)
{
var choice = promptContext.Options.Choices.FirstOrDefault(
c => c.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase));
if (choice != null)
{
promptContext.Recognized.Value = new FoundChoice
{
Value = choice.Value,
};
returntrue;
}
}
return promptContext.Recognized.Succeeded;
}
Now above in the MainDialog declaration change:
C#
// Define the main dialog and its related components.
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
to:
C#
// Define the main dialog and its related components.
AddDialog(new ChoicePrompt(nameof(ChoicePrompt), ChoiceValidator));
This will invoke your validator to look for Adaptive Card input each time a new choice prompt is created.
Open mainDialog.js and find the run method async run(turnContext, accessor)
This method handles incoming activity.
Just after the call dialogSet.add(this); add the following:
JavaScript
// The following check looks for a non-existent text input// plus Adaptive Card input in _activity.value.text// If both conditions exist, the Activity Card text// is copied into the text input field.if(turnContext._activity.text == null
&& turnContext._activity.value.text != null) {
this.logger.log('replacing null text with Activity Card text input');
turnContext._activity.text = turnContext._activity.value.text;
}
If this check finds a non-existent text input from the client, it looks to see if there's input from an Adaptive Card.
If an Adaptive Card input exists at _activity.value.text, it copies this into the normal text input field.
Our validator uses the Serialization helper from com.microsoft.bot.schema to first convert this to a JsonNode,
and then create a trimmed text string for comparison. We'll also need a few other imports to complete this, so add:
to MainDialog.java.
In the validator code, we added the logic flow into the code comments.
This PromptValidator expression is placed into the Using cards sample just after the closed brace public for declaration of MainDialog:
Java
PromptValidator<FoundChoice> validator = (promptContext) -> {
// Retrieves Adaptive Card comment text as JObject.// looks for JObject field "text" and converts that input into a trimmed text// string.
JsonNode jsonNode = Serialization.getAs(promptContext.getContext().getActivity().getValue(), JsonNode.class);
JsonNode textNode = jsonNode != null ? jsonNode.get("text") : null;
String text = textNode != null ? textNode.textValue() : "";
// Logic: 1. if succeeded = true, just return promptContext// 2. if false, see if JObject contained Adaptive Card input.// No = (bad input) return promptContext// Yes = update Value field with JObject text string, return "true".if (!promptContext.getRecognized().getSucceeded() && text != null) {
Optional<Choice> choice = promptContext.getOptions()
.getChoices()
.stream()
.filter(c -> StringUtils.compareIgnoreCase(c.getValue(), text) == 0)
.findFirst();
if (choice.isPresent()) {
promptContext.getRecognized().setValue(new FoundChoice() {
{
setValue(choice.get().getValue());
}
});
return CompletableFuture.completedFuture(true);
}
}
return CompletableFuture.completedFuture(promptContext.getRecognized().getSucceeded());
};
Now above in the MainDialog declaration change:
Java
// Define the main dialog and its related components.
addDialog(new ChoicePrompt("ChoicePrompt"));
to:
Java
// Define the main dialog and its related components.
addDialog(new ChoicePrompt("ChoicePrompt", validator, null));
This will invoke your validator to look for Adaptive Card input each time a new choice prompt is created.
Create and send an activity with suggested actions to the user.
This choice_validator method is placed into the Using cards sample just after the closed brace public for declaration of MainDialog:
Python
@staticmethodasyncdefchoice_validator(prompt_context: PromptValidatorContext) -> bool:if prompt_context.context.activity.value:
text = prompt_context.context.activity.value["text"].lower()
ifnot prompt_context.recognized.succeeded and text:
matching_choices = [choice for choice in prompt_context.options.choices if choice.value.lower() == text]
if matching_choices:
choice = matching_choices[0]
prompt_context.recognized.value = FoundChoice(
value=choice.value,
index=0,
score=1.0
)
returnTruereturn prompt_context.recognized.succeeded
Adaptive Cards are platform-agnostic snippets of UI, authored in JSON, that apps and services can openly exchange. When delivered to a specific app, the JSON is transformed into native UI that automatically adapts to its surroundings. It helps design and integrate light-weight UI for all major platforms and frameworks. In this module, you'll learn how to create engaging messages with Adaptive Cards to create Outlook Actionable Messages and conversations in Microsoft Teams.
Learn send and receive files from bot using Graph APIs for personal, channel, groupchat scopes. Use Teams bot APIs using code samples based on v3 Bot Framework SDK.
Learn how bots send notification messages. See how to retrieve conversation references and test proactive messages. View code samples and design considerations.