When we launched Flare, we could notify you over errors occurring via Slack, Mail, SMS and Webhooks. When we take a look at our database, Slack seems to be the predominant way for teams to communicate.
In the meantime however, we got a lot of questions to add support for alternative notification platforms. Today, we're adding support for sending notifications via Discord and Microsoft Teams.
We took great care in formatting exception just right. If your teams uses Flare and Discord, this is how exceptions will look like in the Discord UI.

And here's how's it looks like when using Microsoft Teams.

In our documentation you can read how you can start using these new notification channels.
How these notification channels are implemented
Behind the scenes, Flare is a Laravel application. Out of the box, Laravel has easy to extend system for sending out notifications.
For Discord, we quickly implemented our own Discord notification channel and message.
This is the code of the DiscordChannel class.
namespace App\Domain\Notification\Services\Channels\Discord;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Illuminate\Notifications\Notification;
class DiscordChannel
{
public function send($notifiable, Notification $notification): void
{
$discordMessage = $notification->toDiscord();
$discordWebhook = $notifiable->routeNotificationForDiscord();
(new Client())->post($discordWebhook, [
RequestOptions::JSON => $discordMessage->toArray(),
]);
}
}
And here's the code for the DiscordMessage class. You'll notice that it's a bit opinionated. For convenience, we've added an errorOccurrence method directly on the class that can format a given ErrorOccurrence model.
namespace App\Domain\Notification\Services\Channels\Discord;
use App\Domain\Error\Models\ErrorOccurrence;
use App\Http\App\Controllers\Projects\ShowProjectController;
use Carbon\Carbon;
class DiscordMessage
{
public const COLOR_SUCCESS = '0b6623';
public const COLOR_WARNING = 'fD6a02';
public const COLOR_ERROR = 'e32929';
protected string $title = '';
protected string $description = '';
protected array $fields = [];
protected ?string $timestamp = null;
protected ?string $footer = null;
protected ?string $color = null;
protected string $url = '';
public function title($title)
{
$this->title = $title;
return $this;
}
public function url($url)
{
$this->url = $url;
return $this;
}
public function description($descriptionLines): self
{
if (! is_array($descriptionLines)) {
$descriptionLines = [$descriptionLines];
}
$this->description .= implode(PHP_EOL, $descriptionLines);
return $this;
}
public function timestamp(Carbon $carbon): self
{
$this->timestamp = $carbon->toIso8601String();
return $this;
}
public function footer(string $footer): self
{
$this->footer = $footer;
return $this;
}
public function success(): self
{
$this->color = static::COLOR_SUCCESS;
return $this;
}
public function warning(): self
{
$this->color = static::COLOR_WARNING;
return $this;
}
public function error(): self
{
$this->color = static::COLOR_ERROR;
return $this;
}
public function fields(array $fields, bool $inline = true): self
{
foreach ($fields as $label => $value) {
$this->fields[] = [
'name' => $label,
'value' => $value,
'inline' => $inline,
];
}
return $this;
}
public function errorOccurrence(ErrorOccurrence $errorOccurrence, string $message): self
{
$projectUrl = action(ShowProjectController::class, $errorOccurrence->error->project->idSlug());
$stage = $errorOccurrence->stage
? "({$errorOccurrence->stage})"
: "";
$this->title("{$errorOccurrence->error->project->name} {$stage}");
$this->url($projectUrl);
$fileLocation = "{$errorOccurrence->error->file}:`{$errorOccurrence->error->line_number}`";
if ($firstFrame = $errorOccurrence->firstFrame()) {
$fileLocation = "{$firstFrame->file}:`{$firstFrame->lineNumber}`";
}
$properties = [
"URL: {$errorOccurrence->seen_at_url}",
$message,
'',
"[See details on Flare]({$errorOccurrence->reportUrl()})",
"**{$errorOccurrence->exception_message}**",
'',
"**File:** {$fileLocation}",
'',
];
if ($firstFrame) {
$codeSnippet = $firstFrame->shortenCodeSnippetWithCurrentLineMark();
$occurredIn = "`{$firstFrame->class}::{$firstFrame->method}`" . PHP_EOL;
$this->fields([
$occurredIn => "```{$errorOccurrence->error->language}\n{$codeSnippet}```",
], inline: false);
}
$this->fields([
'Stage' => $errorOccurrence->stage,
'Exception' => $errorOccurrence->exception_class,
'Occurrence count' => $errorOccurrence->error->occurrence_count,
'Affected users' => $errorOccurrence->error->affected_user_count,
]);
$this->description($properties);
$this->timestamp($errorOccurrence->received_at);
return $this;
}
public function toArray(): array
{
return [
'username' => 'Flare',
'avatar_url' => 'https://flareapp.io/images/flare-avatar.png',
'embeds' => [
[
'title' => $this->title,
'url' => $this->url,
'type' => 'rich',
'description' => $this->description,
'fields' => $this->fields,
'color' => hexdec($this->color),
'footer' => [
'text' => $this->footer ?? '',
],
'timestamp' => $this->timestamp,
],
],
];
}
}
With this in place, we can add a toDiscord method on our notification class.
namespace App\Domain\Error\Notifications\ErrorOccurred;
use App\Domain\Error\Models\ErrorOccurrence;
use App\Domain\Notification\Services\Channels\Discord\DiscordMessage;
class NewErrorOccurredNotification extends Notification
{
public function __construct(
public ErrorOccurrence $errorOccurrence
) {}
// other notifications channels omitted for brevity...
public function toDiscord()
{
return (new DiscordMessage())
->error()
->errorOccurrence(
$this->errorOccurrence,
'A new error occurred',
);
}
}
In closing
We're very happy with these new notification channels, and hope you'll like them as well. In the near future, you can expect more improvements around Flare's notification sytem.
If you want use to support your favourite notification channels, or have suggestions on how to make Flare better, let us know. We're listening!
Continue reading
Lessons from the deep end
20 months ago, we started building Performance Monitoring as Flare’s next big feature, never expecting Laravel’s rapid commercial growth to put us in direct competition with their own tools. This is our honest take on those 20 months went, how we’re adapting to this new reality, and where we’re heading next while staying true to who we are. A dive into the deep end, without knowing how far down it goes.
Alex
Connect your AI agent to Flare to automatically fix production and performance problems in PHP and Laravel projects
You can now use our MCP server to connect your AI agent to Flare. This way your AI has all context it needs to diagnose and fix production and performance problems.
Freek
Subscribe to Backtrace, our quarterly Flare newsletter
No spam, just news & product updates