Send Messages via Microsoft Teams Using Microsoft Graph API in Laravel
If you're looking to send messages to Microsoft Teams users using the Microsoft Graph API, you've come to the right place! We’ve successfully integrated the API to send messages directly to Teams users by leveraging Laravel and Microsoft’s OAuth 2.0 authentication.

Overview
This solution allows you to:
- Authenticate using your Microsoft credentials.
- Retrieve the User ID based on the email address.
- Find or Create a Chat between the logged-in user and the target user.
- Send a Message to the target user via Microsoft Teams.
We have created a service class MSGraphClient
to interact with the Graph API, including methods for token retrieval, user ID lookup, chat creation, and message sending.
1. Environment Setup (.env
file)
Before getting started, make sure to configure the following in your .env
file:
MICROSOFT_TENANT_ID=your_tenant_id
MICROSOFT_CLIENT_ID=your_client_id
MICROSOFT_CLIENT_SECRET=your_client_secret
MICROSOFT_SCOPE=https://graph.microsoft.com/.default
MICROSOFT_USERNAME=your_username_email
MICROSOFT_PASSWORD=your_password
This setup will allow you to authenticate and interact with Microsoft Graph.
2. The MSGraphClient Service Class (Important)
This service class is responsible for authenticating the user, fetching their ID, finding or creating a chat, and sending the message.
namespace
namespace App\Services;
use Illuminate\Support\Facades\Http;
class MSGraphClient
{
public function getMicrosoftToken()
{
$response = Http::asForm()->post('https://login.microsoftonline.com/' . env('MICROSOFT_TENANT_ID') . '/oauth2/v2.0/token', [
'client_id' => env('MICROSOFT_CLIENT_ID'),
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
'scope' => env('MICROSOFT_SCOPE'),
'username' => env('MICROSOFT_USERNAME'),
'password' => env('MICROSOFT_PASSWORD'),
'grant_type' => 'password',
]);
if ($response->successful()) {
return $response->json()['access_token'];
} else {
throw new \Exception("Failed to obtain Microsoft OAuth token: " . $response->body());
}
}
public function getUserId($email)
{
$token = $this->getMicrosoftToken();
$response = Http::withToken($token)->get("https://graph.microsoft.com/v1.0/users/{$email}");
if ($response->successful()) {
return $response->json()['id'];
} else {
throw new \Exception("Failed to retrieve user ID: " . $response->body());
}
}
public function getChatId($targetUserId)
{
$token = $this->getMicrosoftToken();
$loggedInUserId = $this->getUserId(env('MICROSOFT_USERNAME'));
// Check if a chat already exists
$response = Http::withToken($token)->get("https://graph.microsoft.com/v1.0/me/chats");
if ($response->successful()) {
foreach ($response->json()['value'] as $chat) {
if ($chat['chatType'] === 'oneOnOne' && isset($chat['members'][1]) && $chat['members'][1]['id'] === $targetUserId) {
return $chat['id'];
}
}
}
// If no chat exists, create one
$response = Http::withToken($token)->post("https://graph.microsoft.com/v1.0/chats", [
'chatType' => 'oneOnOne',
'members' => [
[
'@odata.type' => '#microsoft.graph.aadUserConversationMember',
'roles' => ['owner'],
'******@odata.bind' => 'https://graph.microsoft.com/v1.0/users/' . $targetUserId
],
[
'@odata.type' => '#microsoft.graph.aadUserConversationMember',
'roles' => ['owner'],
'******@odata.bind' => 'https://graph.microsoft.com/v1.0/users/' . $loggedInUserId
]
]
]);
if ($response->successful()) {
return $response->json()['id'];
} else {
throw new \Exception("Failed to create or find chat: " . $response->body());
}
}
public function sendMessageToUser($email, $messageContent)
{
$targetUserId = $this->getUserId($email);
$chatId = $this->getChatId($targetUserId);
// Send message to the chat
$response = Http::withToken($this->getMicrosoftToken())->post("https://graph.microsoft.com/v1.0/chats/{$chatId}/messages", [
'body' => [
'content' => $messageContent
]
]);
if ($response->successful()) {
return $response->json();
} else {
throw new \Exception("Failed to send message: " . $response->body());
}
}
}
3. Controller Logic
To trigger the message sending functionality, use the following controller method:
public function sendMessage()
{
try {
$email = 'user@example.com'; // Target recipient email
$messageContent = 'This is a system triggered message from the OMS application.'; // Message content
$response = $this->msGraphClient->sendMessageToUser($email, $messageContent);
return response()->json(['message' => 'Message sent successfully!', 'response' => $response], 200);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
Issue Encountered When Using Azure Bot Service Bot ID to Send Messages
When we attempted to use the Azure Bot Service Bot ID for sending messages to Microsoft Teams, we encountered an error:
{
{ "error": { "code": "Unauthorized", "message": "Message POST is allowed in application-only context only for import purposes. Refer to https://docs.microsoft.com/microsoftteams/platform/graph-api/import-messages/import-external-messages-to-teams for more details." } }
This error occurred because the Microsoft Graph API restricts sending messages through bot IDs for external (non-bot) user interactions. So the solution to this issue was to authenticate via OAuth and use the authenticated user's access token to send messages, rather than relying on the bot ID. This ensures that the message is sent within the appropriate context, following the application-only restrictions outlined by Microsoft.