PHPackages                             wapplersystems/microsoft-graph-mailer - 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. wapplersystems/microsoft-graph-mailer

ActiveTypo3-cms-extension[Mail &amp; Notifications](/categories/mail)

wapplersystems/microsoft-graph-mailer
=====================================

Microsoft Graph mailer transport for TYPO3 — sends emails via Microsoft 365 Graph API with OAuth2 (uses wapplersystems/oauth-service)

14.0.0(1mo ago)15GPL-2.0-or-laterPHPPHP &gt;=8.2

Since Jun 4Pushed 1mo agoCompare

[ Source](https://github.com/WapplerSystems/t3-microsoft-graph-mailer)[ Packagist](https://packagist.org/packages/wapplersystems/microsoft-graph-mailer)[ Docs](https://github.com/WapplerSystems/t3-microsoft-graph-mailer)[ RSS](/packages/wapplersystems-microsoft-graph-mailer/feed)WikiDiscussions release/v14 Synced 1w ago

READMEChangelogDependencies (4)Versions (3)Used By (0)

Microsoft Graph Mailer for TYPO3
================================

[](#microsoft-graph-mailer-for-typo3)

Sends TYPO3 system emails via the **Microsoft Graph API** using OAuth 2.0 client credentials. Built on top of [`wapplersystems/oauth-service`](https://github.com/WapplerSystems/t3-oauth-service)for token acquisition, caching, refresh and BE-configurable client setup.

No SMTP. No mailbox password. No interactive user consent needed.

Why
---

[](#why)

Office 365 / Exchange Online is phasing out Basic Authentication for SMTP AUTH. Modern delivery uses the Microsoft Graph `sendMail` endpoint with an OAuth2 bearer token. This extension wires that endpoint into TYPO3's existing Symfony Mailer pipeline so any `MailerInterface` consumer sends through Graph transparently.

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

[](#requirements)

- TYPO3 v14
- PHP 8.2+
- `wapplersystems/oauth-service` ≥ release with `tx_oauthsvc_client.metadata`
- A Microsoft 365 tenant + Entra ID app registration with the `Mail.Send` **Application** permission (admin-consent granted)
- A dedicated service mailbox restricted via Application Access Policy

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

[](#installation)

```
composer require wapplersystems/microsoft-graph-mailer
typo3 extension:setup
```

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

[](#configuration)

### 1. Azure / Microsoft Entra ID — App registration

[](#1-azure--microsoft-entra-id--app-registration)

1.  → **Applications → App registrations → New registration**
    - Name: `TYPO3 Mailer – yourdomain.com`
    - Account type: *Single tenant*
    - Redirect URI: leave empty (app-only flow, no user login)
2. **API permissions → Microsoft Graph → Application permissions**
    - Add `Mail.Send`
    - Click **Grant admin consent**
3. **Certificates &amp; secrets → New client secret**
    - Copy the *Value* (only shown once)
4. Note from the **Overview** page:
    - Directory (tenant) ID
    - Application (client) ID

### 2. Restrict the app to one mailbox (Exchange Online PowerShell)

[](#2-restrict-the-app-to-one-mailbox-exchange-online-powershell)

Without this step the app could send from *any* mailbox in the tenant.

```
Connect-ExchangeOnline

New-DistributionGroup -Name "TYPO3MailerAllowedSenders" `
  -Type Security -Members noreply@yourdomain.com

New-ApplicationAccessPolicy `
  -AppId  `
  -PolicyScopeGroupId TYPO3MailerAllowedSenders@yourdomain.com `
  -AccessRight RestrictAccess `
  -Description "TYPO3 Mailer restricted to noreply mailbox"
```

### 3. TYPO3 — OAuth Services backend module

[](#3-typo3--oauth-services-backend-module)

Go to **System → OAuth Services → Clients → New**:

FieldValueProvider`microsoft_graph`Active✅Client ID*Application (client) ID from Azure*Client Secret*Client secret value from Azure*Scopes`https://graph.microsoft.com/.default`Metadata (JSON)`{"tenant_id": "", "sender_upn": "noreply@yourdomain.com"}`Save. The token is fetched lazily on the first mail send and cached by `oauth-service` (TYPO3 `oauth_service` cache backend) for its `expires_in`lifetime.

### 4. Wire the transport

[](#4-wire-the-transport)

Add to `config/system/additional.php` (or set via *Admin Tools → Settings → Configure Installation-wide Options → MAIL*):

```
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] =
    \WapplerSystems\MicrosoftGraphMailer\Mailer\GraphTransport::class;
```

That is the only TYPO3-side configuration. No DSN, no SMTP settings, no mailbox password. The transport reads `sender_upn` and `tenant_id` from the OAuth client's metadata at send time.

### 5. (Recommended) Scheduler

[](#5-recommended-scheduler)

`oauth-service` ships two console commands. For client-credentials only the cache invalidation matters, but the monitor catches misconfigured credentials early:

```
typo3 oauth-service:monitor-connections    # daily
```

Testing
-------

[](#testing)

```
typo3 microsoft-graph-mailer:test you@example.com
```

A successful send returns silently. On error the transport throws a `GraphMailerException` with the Microsoft `request-id` header — quote that in any Microsoft support case.

Failure handling — the spool fallback
-------------------------------------

[](#failure-handling--the-spool-fallback)

To avoid silently dropping emails when Microsoft Graph rejects a request (no OAuth client configured, expired secret, license issue, 401/403/404 from `sendMail`, …), the transport writes the original message as an `.eml` file plus a `.json` payload record to a spool directory and lets the caller proceed. Default location:

```
/typo3-mail-spool/

```

Each undelivered message becomes a pair:

- `YYYYMMDD-HHMMSS-recipient_at_domain-xxxxxxxx.eml` — full RFC 5322 message; openable in any mail client, useful for human inspection or manual resend
- `YYYYMMDD-HHMMSS-recipient_at_domain-xxxxxxxx.json` — failure metadata (timestamp, reason, recipients, subject) plus the original Microsoft Graph `sendMail` payload (`graph_payload`) so an automated retry does not need to re-parse the .eml

### Audit

[](#audit)

```
typo3 microsoft-graph-mailer:list-undelivered
```

shows everything currently in the spool with the original failure reason and retry count.

### Automatic retry (recommended)

[](#automatic-retry-recommended)

`typo3 microsoft-graph-mailer:resend-undelivered` iterates the spool and re-posts each entry's `graph_payload` to Microsoft Graph. On success the pair is deleted; on failure the entry stays in the spool with `retry_count` incremented and `last_retry_error` recorded.

Options:

- `--limit N` — process at most N entries per run (default 50)
- `--max-age-days D` — delete (abandon) entries older than D days; 0 disables
- `--dry-run` — list what would be retried without contacting Graph
- `--dir PATH` — override the spool directory

Schedule this command in the TYPO3 Scheduler module as **"Microsoft Graph: resend undelivered emails"** (registered task) every five minutes. The task exposes a *Batch size* and *Max age (days)* field in the BE form.

### Configuration

[](#configuration-1)

```
// Override the spool path:
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_graph_fallback_directory'] = '/var/typo3-mail-spool/';

// Disable the fallback entirely (re-raises GraphMailerException, may break form-submits etc.):
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_graph_fallback_directory'] = false;
```

Sender address &amp; `defaultMailFromAddress`
---------------------------------------------

[](#sender-address--defaultmailfromaddress)

The transport calls `/users/{sender_upn}/sendMail` — Graph treats that mailbox as the authenticated sender. The email's `From:` header is taken from the Symfony `Email::getFrom()` (typically TYPO3's `$GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']`) and placed into `message.from` of the Graph payload.

When `defaultMailFromAddress` differs from `sender_upn`, Exchange Online requires the `sender_upn` mailbox to hold **Send As** (or *Send on Behalf*) permission for the `defaultMailFromAddress` mailbox. Without it, Graph rejects the request:

```
HTTP 403  ErrorSendAsDenied
"The user account which was used to submit this request does not have the
 right to send mail on behalf of the specified sending account."

```

Fix in Exchange Online PowerShell:

```
Add-RecipientPermission -Identity "" `
    -Trustee "" `
    -AccessRights SendAs -Confirm:$false
```

Or in the Microsoft 365 Admin Center: **Active users → &lt;defaultMail- FromAddress&gt; → Mail → Manage mailbox permissions → Send as →** add ``.

Notes:

- `` must be a real mailbox or Shared Mailbox. Pure aliases of `sender_upn` cannot carry their own permissions — convert the alias into a Shared Mailbox, or align `defaultMailFromAddress` with `sender_upn`.
- *Send on Behalf* (`Set-Mailbox -GrantSendOnBehalfTo`) is a valid alternative but adds a `Sender:` header, so recipients see *"&lt;sender\_upn&gt; on behalf of &lt;from&gt;"* in their mail client. Usually not desired.
- Permission changes propagate in Exchange Online within minutes (up to one hour). Flush the TYPO3 token cache (`typo3 cache:flush`) before retrying. Until the permission lands, failed sends accumulate in the spool — re-flight them via `typo3 microsoft-graph-mailer:resend-undelivered` once it works.

Multiple senders
----------------

[](#multiple-senders)

The transport always targets the *first* active `microsoft_graph` OAuth client. To send from a different mailbox in the same tenant, change the `sender_upn` metadata value. To use a different tenant entirely, create a second client row and deactivate the first.

Limitations
-----------

[](#limitations)

- **`saveToSentItems` is hard-set to `false`** — the service mailbox is typically `noreply@…` and shouldn't accumulate sent mail. If you need Sent-Folder copies, fork the transport.
- **Internet message headers** are limited by Graph to `x-`-prefixed custom headers. Standard MIME headers (From/To/Subject/…) are mapped to the corresponding Graph message fields automatically.
- **Throttling**: Microsoft Graph applies a 10,000 messages/24h limit per app per mailbox. For bulk newsletter traffic use a dedicated newsletter service (CleverReach, Mailchimp, …) instead.

License
-------

[](#license)

GPL-2.0-or-later

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance91

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

50d ago

### Community

Maintainers

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

---

Top Contributors

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

---

Tags

maileroauth2extensiontypo3Office365Microsoft graph

### Embed Badge

![Health badge](/badges/wapplersystems-microsoft-graph-mailer/health.svg)

```
[![Health](https://phpackages.com/badges/wapplersystems-microsoft-graph-mailer/health.svg)](https://phpackages.com/packages/wapplersystems-microsoft-graph-mailer)
```

###  Alternatives

[laravel/framework

The Laravel Framework.

34.8k543.8M20.5k](/packages/laravel-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

19566.0M1.8k](/packages/drupal-core)[kimai/kimai

Kimai - Time Tracking

4.8k9.0k1](/packages/kimai-kimai)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6942.5M425](/packages/drupal-core-recommended)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M600](/packages/shopware-core)

PHPackages © 2026

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