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

Laravel Teams Notification Laravel Package

osa-eg/laravel-teams-notification

Send Laravel notifications to Microsoft Teams with a simple, fluent API. This package adds a Teams channel so you can post rich message cards via webhooks, keeping your team informed straight from your Laravel app.

Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require osa-eg/laravel-teams-notification
php artisan vendor:publish --tag=teams-notification-config

Put the webhook URL in .env rather than in code:

TEAMS_WEBHOOK_URL=https://prod-00.westus.logic.azure.com:443/workflows/.../triggers/manual/paths/invoke?...

2. Create the webhook on the Teams side first

In the target channel: Workflows → "Post to a channel when a webhook request is received" → pick channel → copy the generated URL. This is the Power Automate workflow URL, not the legacy Office 365 Connector URL — they are different products and different payloads. Getting this wrong is the most common first-hour failure.

3. Where to look first

  • config/teams-notification.php — default webhook, timeouts, HTTP options.
  • src/ — the channel class and the message builder (conventionally TeamsChannel / TeamsMessage; confirm exact casing in the README, since Laravel channel packages vary).
  • The README's payload example — it shows the Adaptive Card shape the package produces.

4. First use case: a five-line smoke test

Send one message on demand before wiring up notifications, so you isolate webhook problems from notification problems:

app(TeamsChannel::class)->send($webhookUrl, TeamsMessage::create()
    ->title('Deploy finished')
    ->text('api@' . config('app.env') . ' is live'));

If that posts, everything downstream is just Laravel plumbing.

Implementation Patterns

Notification class (the main path). Standard Laravel channel contract — via() returns the Teams channel, toTeams() returns the message:

class DeployFailed extends Notification implements ShouldQueue
{
    public function via($notifiable): array { return [TeamsChannel::class]; }

    public function toTeams($notifiable): TeamsMessage
    {
        return TeamsMessage::create()
            ->title('Deploy failed')
            ->text("**{$this->env}** · {$this->commit}")
            ->facts(['Pipeline' => $this->pipeline, 'Stage' => $this->stage])
            ->link('Open run', $this->url);
    }
}

On-demand routing — the pattern you will actually use most. Teams destinations are per-team or per-tenant, not per-User model. Storing a webhook URL on a Team/Project model and routing on demand keeps you out of the Notifiable traits:

Notification::route('teams', $project->teams_webhook_url)
    ->notify(new DeployFailed($deploy));

Centralise webhook resolution. Wrap destination lookup in one small service or a RouteNotificationForTeams trait so you have a single place to fall back to config('teams-notification.webhook'), skip when unset, and enforce that URLs are never interpolated from user input.

Wire it into the failure seams. These three hooks give the highest value per line of code:

  • App\Exceptions\Handler::report() — alert on unhandled exceptions, throttled.
  • $schedule->command(...)->onFailure(fn () => ...) — dead cron jobs.
  • Job::failed() / Queue::failing() — queue backlogs.

Guard against noise. Add an env/config gate (TEAMS_ALERTS_ENABLED) and a cache-based throttle (Cache::add("teams:{$key}", true, 300)) inside the alerter, so a flapping service produces one message per five minutes, not one per request.

Testing. Notification::fake() + assertSentOnDemand() for the notification layer; Http::fake() for the channel layer. Assert the outgoing JSON contains your title/text rather than asserting the whole card — the card envelope is the package's concern, not yours.

Gotchas and Tips

The payload schema is not the legacy one. The old Office 365 Connector used @type: MessageCard with sections/potentialAction; the Power Automate workflow uses AdaptiveCard. If you copy examples from 2021-era blog posts you will get a 202 Accepted and a silent no-op, or a 400 with an opaque body. Trust the package's builder over hand-rolled arrays.

A success response does not mean a posted message. Workflow endpoints may return 202 or a 200 with an empty/text body rather than JSON. Do not chain ->json() unconditionally on the response; check status and treat non-2xx as failure.

Treat the URL as a bearer secret. Anyone with it can post to the channel. Keep it in .env or your secret store, never log it (Log::error on a failed request will happily dump the full URL in the exception message — scrub it), and rotate via a new workflow if it leaks.

Set an explicit HTTP timeout. A hanging webhook turns a failed deploy notification into a hung queue worker. Configure a short connect/total timeout in the package config, and always send alerting notifications from a queued listener or ShouldQueue notification, never inline in a request cycle.

Rate limits are real. Power Automate throttles per connection. Bulk loops (foreach ($failed as $item) notify(...)) will start dropping messages. Batch into one card — or one card per digest interval — instead of one message per row.

Card size and formatting limits. Adaptive Cards have a payload ceiling (tens of KB) and a limited markdown subset: **bold**, _italic_, links, lists. HTML and tables silently render as literal text. Long exception traces should be truncated and linked to your log viewer, not pasted.

Config caching. env() calls outside config/ return null under php artisan config:cache. Put the webhook in the config file once and read it with config() everywhere else.

Local and CI safety. Set the webhook to null (or point it at a sink channel) in .env.testing and local envs; combine with a Notification::fake() in the test suite so a stray test run cannot page the on-call channel.

Extension points worth knowing. If the built-in builder lacks a card element you need, subclass the message class and override the payload array rather than editing vendor files — and check whether the channel exposes a way to swap the message class in config. That is the clean upgrade path when the package lags the Adaptive Card schema.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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