Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Microsoft Teams Laravel Package

laravel-notification-channels/microsoft-teams

Send notifications to Microsoft Teams from your Laravel app. This channel plugs into Laravel's notification system so you can post rich MessageCard or Adaptive Card messages to Teams channels via incoming webhooks, all through a clean, familiar API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the channel:

composer require laravel-notification-channels/microsoft-teams

Add a webhook entry to config/services.php (the package reads it from your app config, not its own file, so nothing needs publishing):

'microsoft_teams' => [
    'webhook_url' => env('MICROSOFT_TEAMS_WEBHOOK_URL'),
],

Create an incoming webhook for the target Teams channel (Connectors → Incoming Webhook, or the newer Workflows "Post to a channel when a webhook request is received" trigger), and drop the URL into .env. Treat it as a secret — it is a bearer credential for that channel.

Then generate a notification and implement the channel:

php artisan make:notification DeploymentFailed
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;

public function via($notifiable): array
{
    return [MicrosoftTeamsChannel::class];
}

public function toMicrosoftTeams($notifiable): MicrosoftTeamsMessage
{
    return MicrosoftTeamsMessage::create()
        ->to(config('services.microsoft_teams.webhook_url'))
        ->type('danger')
        ->title('Deployment failed')
        ->content("`{$this->release}` failed on {$this->environment}.")
        ->button('View logs', $this->logUrl);
}

First use case: production alerting. Wire DeploymentFailed, Queue::failing(), or your exception handler's report() hook into a single Teams channel before you build anything else — that gives you the fastest feedback loop on whether the webhook, payload shape, and rendering all behave.

Where to look first: the readme for the copy-paste flow, then src/MicrosoftTeamsMessage.php for the full fluent surface (to, title, content, type, button, and the card/section builders) and src/MicrosoftTeamsChannel.php to see exactly how the payload is POSTed. Those two files answer 90% of day-to-day questions.

Implementation Patterns

Route the webhook, don't hardcode it. Implement routeNotificationForMicrosoftTeams() on the notifiable (a Team, Project, Tenant model) and the channel will resolve the URL for you:

public function routeNotificationForMicrosoftTeams($notification): ?string
{
    return $this->teams_webhook_url;
}

This is what makes the channel reusable across tenants or per-customer channels instead of a single global env var.

Fire-and-forget to arbitrary channels with on-demand notifications — ideal for ad-hoc alerts from jobs, commands, and tinker sessions:

Notification::route('MicrosoftTeams', $webhookUrl)
    ->notify(new DeploymentFailed($release, $environment, $logsUrl));

Always queue. Teams webhooks are an external HTTP call in the request path; add implements ShouldQueue to the notification and give it $tries/backoff. Combine with a ->via() guard so noisy local/staging environments don't spam the channel:

public function via($notifiable): array
{
    return app()->isProduction() ? [MicrosoftTeamsChannel::class] : [];
}

Map severity to type(). A consistent success / info / warning / danger convention across notifications makes the channel visually scannable — engineers triage red cards first. Wrap this in a small base notification or a TeamsAlert value object so every alert in the app formats title/context/action-button the same way.

Build a link-out habit. A card without a button is a dead end. Always attach the log viewer, dashboard, PR, or runbook URL so the recipient can act without asking.

Integrate at the framework seams: Queue::failing(fn ($e, $job) => ...), the exception handler's report(), Schedule::call() for daily digests via Notification::route(...), or a DeploymentSucceeded event listener. These are where Teams notifications earn their keep versus reading dashboards.

Test with Http::fake(). The channel posts through Laravel's HTTP client, so:

Http::fake();
Notification::route('MicrosoftTeams', 'https://example.test/webhook')
    ->notify(new DeploymentFailed('v1.2.3', 'production', 'https://logs'));

Http::assertSent(fn ($request) =>
    $request['title'] === 'Deployment failed'
    && str_contains($request['text'], 'v1.2.3')
);

Pair with Http::preventStrayRequests() in tests so a missing fake fails loudly instead of posting to a real channel.

Gotchas and Tips

Verify the endpoint before writing code. Microsoft has been retiring classic Office 365 connectors in favour of Power Automate Workflows. The package emits the legacy MessageCard JSON shape; depending on how your tenant is configured, you may need the Workflows HTTP trigger and to confirm it accepts that payload. Post one hand-crafted curl to the URL first — debugging "is it Teams or is it my code?" later is far more expensive.

No retries, no swallowing. The channel throws on a non-2xx response. Inside a queued notification that's usually what you want (the queue retries), but in a synchronous call it will bubble into your request. Wrap best-effort alerts in try/catch + Log::warning, or register a NotificationFailed listener. Silently losing an alert is worse than a noisy log; failing a user request because Teams is down is worse still.

Respect rate limits. Webhooks tolerate only a few messages per second (~4/sec per connector). Never notify inside a loop over records — batch into one card, or throttle. Bursting is the most common way teams get their webhook temporarily blocked.

Payload size and formatting limits. Cards cap out around 28 KB, and the content field supports limited Markdown — bold, inline code, links, lists — not HTML. Truncate exception traces and stack dumps (Str::limit($e->getTraceAsString(), 1500)) before they reach the card, or Teams will reject the payload or render an unreadable wall.

Missing route = confusing failure. If neither ->to() nor routeNotificationForMicrosoftTeams() returns a URL, the send fails with an unhelpful error. Guard the route method with a null return plus a log line so misconfigured tenants are visible rather than mysterious.

Escape/limit user-supplied text. Content interpolated into markdown can be mangled by backticks, pipes, and brackets. Sanitise names and messages, and be deliberate about anything that looks like a mention — unexpected @ pings in an incident channel are a fast way to lose goodwill.

Escape hatch for richer cards. If you need Adaptive Cards or Workflows-specific payloads beyond the fluent builder, inspect MicrosoftTeamsMessage::getPayload() to see exactly what's being sent, then either subclass the message or write a thin custom channel that reuses the package's HTTP transport. Don't fight the fluent API for formats it wasn't designed for.

Give yourself a dev channel. A dedicated #bot-testing webhook plus a TEAMS_ALERTS_ENABLED=false flag keeps iteration cheap and keeps production channels meaningful.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource