PHPackages                             metalogico/laravel-mail-beat - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Mail &amp; Notifications](/categories/mail)
4. /
5. metalogico/laravel-mail-beat

ActiveLibrary[Mail &amp; Notifications](/categories/mail)

metalogico/laravel-mail-beat
============================

Intercept outgoing Laravel emails and ship metadata to an OpenTelemetry-compatible collector via OTLP/HTTP.

00PHPCI passing

Since Apr 10Pushed 2mo agoCompare

[ Source](https://github.com/metalogico/laravel-mail-beat)[ Packagist](https://packagist.org/packages/metalogico/laravel-mail-beat)[ RSS](/packages/metalogico-laravel-mail-beat/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (2)Used By (0)

laravel-mail-beat
=================

[](#laravel-mail-beat)

A Laravel package that intercepts every outgoing email and ships metadata to an OpenTelemetry-compatible collector via OTLP/HTTP. Install it across multiple Laravel applications to build a centralized email observability dashboard without touching your existing mail code.

**No migrations. No schema changes. No extra dependencies. A dead collector never breaks email delivery.**

---

Requirements
------------

[](#requirements)

DependencyVersionPHP8.1+Laravel10.x, 11.x, 12.x, 13.x---

Installation
------------

[](#installation)

```
composer require metalogico/laravel-mail-beat
```

The package registers itself automatically via Laravel's package auto-discovery. No service provider registration required.

Add the collector endpoint to your `.env`:

```
MAIL_BEAT_ENDPOINT=https://your-collector.example.com
```

That's it. Every email sent through Laravel's mail system will now emit an OTLP log record to your collector.

---

Configuration
-------------

[](#configuration)

By default, no config file is needed. If you want to customise settings, publish the config:

```
php artisan vendor:publish --tag=mail-beat-config
```

This creates `config/mail-beat.php`:

```
return [
    'endpoint'  => env('MAIL_BEAT_ENDPOINT', 'http://localhost:4318'),
    'timeout'   => env('MAIL_BEAT_TIMEOUT', 2),
    'anonymize' => env('MAIL_BEAT_ANONYMIZE', true),
    'queue' => [
        'connection' => env('MAIL_BEAT_QUEUE_CONNECTION'),
        'name'       => env('MAIL_BEAT_QUEUE', 'default'),
    ],
];
```

### Environment variables

[](#environment-variables)

VariableDefaultDescription`MAIL_BEAT_ENDPOINT``http://localhost:4318`Base URL of your OTLP/HTTP collector`MAIL_BEAT_TIMEOUT``2`HTTP timeout in seconds for collector requests`MAIL_BEAT_ANONYMIZE``true`Anonymize recipient addresses before shipping (see [Privacy &amp; GDPR](#privacy--gdpr))`MAIL_BEAT_QUEUE_CONNECTION`*(project default)*Queue connection to use for beat jobs`MAIL_BEAT_QUEUE``default`Queue name to use for beat jobs---

How it works
------------

[](#how-it-works)

1. Laravel fires a `MessageSent` event after every outgoing email.
2. `MailSentListener` catches the event, extracts metadata, and dispatches a `SendMailBeatJob`.
3. The job builds an OTLP/HTTP JSON payload and POSTs it to `{MAIL_BEAT_ENDPOINT}/v1/logs`.
4. The entire HTTP call is wrapped in `rescue()` — any exception (network error, timeout, unreachable collector) is silently swallowed. Your email was already sent; the beat is best-effort.

The job inherits your project's queue configuration. If your app uses `sync`, the beat fires immediately in the same request. If your app uses `redis` or `database`, the beat is queued normally — no extra workers needed.

---

Data sent to the collector
--------------------------

[](#data-sent-to-the-collector)

Each email produces one OTLP log record. The payload follows the [OTLP/HTTP JSON](https://opentelemetry.io/docs/specs/otlp/#otlphttp) specification.

### Resource attributes

[](#resource-attributes)

Attached at the resource level, enabling cross-service aggregation in your dashboard:

AttributeSource`service.name``config('app.name')``deployment.environment``config('app.env')`### Log record attributes

[](#log-record-attributes)

AttributeDescription`mail.to`Recipient address(es), comma-separated. Anonymized by default (see [Privacy &amp; GDPR](#privacy--gdpr))`mail.from`Sender address(es), comma-separated`mail.subject`Email subject`mail.size_bytes`Full serialized message size in bytesThe log record body is set to the email subject for human-readable display in log viewers.

### Example payload

[](#example-payload)

```
{
  "resourceLogs": [{
    "resource": {
      "attributes": [
        { "key": "service.name",            "value": { "stringValue": "my-app" } },
        { "key": "deployment.environment",  "value": { "stringValue": "production" } }
      ]
    },
    "scopeLogs": [{
      "scope": { "name": "laravel-mail-beat" },
      "logRecords": [{
        "timeUnixNano": "1712700000000000000",
        "severityText": "INFO",
        "body": { "stringValue": "Welcome to my-app!" },
        "attributes": [
          { "key": "mail.to",         "value": { "stringValue": "****@example.com" } },
          { "key": "mail.from",       "value": { "stringValue": "hello@my-app.com" } },
          { "key": "mail.subject",    "value": { "stringValue": "Welcome to my-app!" } },
          { "key": "mail.size_bytes", "value": { "intValue": 4821 } }
        ]
      }]
    }]
  }]
}
```

---

Queue behaviour
---------------

[](#queue-behaviour)

The beat job respects your application's queue configuration. You can optionally isolate beat traffic to a dedicated queue to keep it separate from your main workload.

`QUEUE_CONNECTION``MAIL_BEAT_QUEUE_CONNECTION`Result`sync`*(not set)*Beat fires synchronously in the same request`database`*(not set)*Beat queued on `default` via database driver`redis`*(not set)*Beat queued on `default` via redis driver`redis``redis` + `MAIL_BEAT_QUEUE=telemetry`Beat isolated on `telemetry` queue via redisTo isolate beat jobs:

```
MAIL_BEAT_QUEUE_CONNECTION=redis
MAIL_BEAT_QUEUE=telemetry
```

---

Privacy &amp; GDPR
------------------

[](#privacy--gdpr)

By default, the local part of every recipient address is replaced with asterisks before the data leaves your application:

```
user@example.com  →  ****@example.com

```

The domain is preserved to allow domain-level analytics (e.g. "how many emails went to gmail.com?"). The exact address is never shipped to the collector.

This behaviour is enabled by default to align with GDPR's data minimisation principle. Recipient email addresses are personal data; the collector does not need them to provide observability value.

To disable anonymization (e.g. your collector is a trusted, compliant internal system):

```
MAIL_BEAT_ANONYMIZE=false
```

> `mail.from` (the sender) is never anonymized, as it is typically a system address belonging to your own application.

---

Out of scope
------------

[](#out-of-scope)

This package is a data producer only. The following are intentionally not supported:

- Email open / click tracking
- Storing emails in the local database
- Delivery status webhooks or callbacks
- Retry logic for failed collector requests
- Batching multiple emails into one OTLP request
- Any dashboard or UI

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance58

Moderate activity, may be stable

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity13

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/c4529a897262128624040a5974d44385389187eebe151c098de1e8030645c257?d=identicon)[metalogico](/maintainers/metalogico)

---

Top Contributors

[![metalogico](https://avatars.githubusercontent.com/u/7287030?v=4)](https://github.com/metalogico "metalogico (4 commits)")

### Embed Badge

![Health badge](/badges/metalogico-laravel-mail-beat/health.svg)

```
[![Health](https://phpackages.com/badges/metalogico-laravel-mail-beat/health.svg)](https://phpackages.com/packages/metalogico-laravel-mail-beat)
```

###  Alternatives

[mattketmo/email-checker

Throwaway email detection library

2742.1M5](/packages/mattketmo-email-checker)[ifightcrime/bootstrap-growl

Pretty simple jQuery plugin that turns standard Bootstrap alerts into 'Growl-like' notifications.

80113.0k](/packages/ifightcrime-bootstrap-growl)[sarfraznawaz2005/noty

Laravel package to incorporate noty flash notifications into laravel.

324.5k](/packages/sarfraznawaz2005-noty)[andheiberg/notify

A site notification package for laravel.

119.1k1](/packages/andheiberg-notify)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
