PHPackages                             madjeek-web/symfony-mailjet-bundle - 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. [HTTP &amp; Networking](/categories/http)
4. /
5. madjeek-web/symfony-mailjet-bundle

ActiveSymfony-bundle[HTTP &amp; Networking](/categories/http)

madjeek-web/symfony-mailjet-bundle
==================================

A modern, async-ready Symfony 7 bundle for Mailjet — PHP 8.3, Messenger, HttpClient, Webhooks &amp; full test coverage.

v1.0.0(4mo ago)60MITPHPPHP &gt;=8.3CI passing

Since Feb 23Pushed 4mo agoCompare

[ Source](https://github.com/madjeek-web/symfony-mailjet-bundle)[ Packagist](https://packagist.org/packages/madjeek-web/symfony-mailjet-bundle)[ Docs](https://github.com/madjeek-web/symfony-mailjet-bundle)[ RSS](/packages/madjeek-web-symfony-mailjet-bundle/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (10)Versions (2)Used By (0)

 [![](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/symfony-framework.webp)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/symfony-framework.webp) [![](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/y-symfony.jpg)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/y-symfony.jpg)

symfony-mailjet-bundle
======================

[](#symfony-mailjet-bundle)

> **A modern, async-ready Symfony 7 bundle for sending emails via Mailjet.**Built with PHP 8.3, Symfony Messenger, HttpClient, Webhooks &amp; 100% test coverage.

[![CI](https://github.com/madjeek-web/symfony-mailjet-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/madjeek-web/symfony-mailjet-bundle/actions)[![PHPStan Level 9](https://camo.githubusercontent.com/b72adb1f27170ecf486459c4b07e920bb3db2b464444bce8277e018270665646/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e)](https://phpstan.org/)[![PHP 8.3+](https://camo.githubusercontent.com/c8d8dad6beb757a2b8acba331d16140813699543b88a37af0a81f20bd35f61de/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e332532422d626c7565)](https://www.php.net/)[![Symfony 7](https://camo.githubusercontent.com/4ffba668c1acc64e3d3c942fe47359ffe900756c98fe9afc0e2b0dfdda18bac5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53796d666f6e792d372e782d626c61636b)](https://symfony.com/)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](https://opensource.org/licenses/MIT)

---

### symfony-mailjet-bundle, what is it exactly ?

[](#symfony-mailjet-bundle-what-is-it-exactly-)

When you use a website and receive an automated email like "Welcome!", "Your order is confirmed", or "Reset your password"... someone had to program that. This project is a free toolkit that developers can download and integrate into their application to send these emails automatically, through a specialized service called Mailjet.

What makes this project professional is the quality of the work provided: the code is fully verified by automated tests (like a safety net), documented, and secured according to enterprise standards. It is published as open source, meaning it's freely accessible to developers worldwide, who can use it or contribute to it.

It's a professional tool, generously shared with the community.

What this project does :

Symfony is a tool for building websites in PHP. It can do a lot of things, but it doesn't know how to send emails via Mailjet. Mailjet is an external service, like a digital postal service.

This project acts as a bridge between the two.

Without this project, a developer wanting to send an email via Mailjet in their Symfony application would have to write all the connection, formatting, security, and error-handling code themselves... taking several days of work.

With this project, they simply write :

```
$emailSender->sendNow($email); // ← that's it
And the email is sent. The project takes care of everything else behind the scenes.

It's like a universal power adapter: Symfony is the wall outlet, Mailjet is the device, and this bundle is the adapter that connects them properly.
```

[![symfony mailjet bundle page demo](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/Framework-Symfony-bg-black.png)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/Framework-Symfony-bg-black.png)

### Why send emails from Symfony rather than with your regular email service ?

[](#why-send-emails-from-symfony-rather-than-with-your-regular-email-service-)

When you send an email from your Gmail or Outlook inbox, you're using a human interface: you write a message, you click "Send", and one single person receives it. It's manual.

But when your website needs to automatically send 100 order confirmation emails in the middle of the night, or 10,000 personalized newsletters, your small regular email service can't keep up. It will:

Limit the number of sends (often 500 per day max)

Filter your emails as spam if you send too many

Crash if too many people sign up at the same time

The fundamental difference: With a regular email service, it's a human who sends. With Symfony + Mailjet, it's the code that sends, in an automated, industrialized, and reliable way.

Symfony is not going to "open Gmail" and click on buttons. It will communicate directly with Mailjet's servers via an API (a machine-to-machine language) to deliver hundreds of emails in one second, with precise statistics: who opened, who clicked, who didn't receive...

The real purpose: To ensure that your emails actually arrive in the inbox (not in spam), that it's reliable 24/7, and that your site remains fast even when sending tons of emails. Your personal email service is designed for you to communicate yourself, not to do the work of an industrial mail carrier for your website.

It's a bit like if you wanted to deliver pizzas. Your personal email inbox is your bike: it's perfect for bringing a pizza to your friend who lives nearby. But if suddenly 500 people order pizzas at the same time, you're not going to make it with your bike, you're going to be overwhelmed and the pizzas will arrive cold or not at all.

Symbole with Mailjet, it's like having a fleet of scooters with professional delivery drivers. They can deliver hundreds of orders at once, very quickly, and on top of that they tell you precisely who received their pizza, who ate it, and who wasn't home. Your little personal bike can't do all that.

The right question to ask
-------------------------

[](#the-right-question-to-ask)

The question "Can you send emails in Symfony with Mailjet?" doesn't really make sense, a bit like asking: " Can you cook pasta with a pressure cooker ? "

Actually, both options are possible :

You can cook pasta without a pressure cooker (in a normal pot)

You can cook pasta with a pressure cooker

But the pressure cooker won't cook the pasta all by itself for you

Here's the right way to understand Mailjet is just a shipping service: It's like the Post Office or FedEx. Mailjet has super powerful servers that can send millions of emails very fast, with precise statistics, while avoiding spam.

Symfony is a letter writer: It's your PHP code that decides when to send an email, to whom, with which text. For example : "When a user signs up, create a welcome email with their first name."

So the real question is: "Can Symfony entrust its emails to Mailjet so that it sends them out ?" Answer : Yes, 100 %. Symfony prepares the email content (the text, the recipient), then it gives it to Mailjet which takes care of fast and reliable shipping. It's like if you wrote a letter (Symfony) and gave it to FedEx (Mailjet) to send it super fast everywhere in the world, rather than putting it yourself in a neighborhood mailbox.

You don't choose between Symfony and Mailjet. You choose to use them together :

Symfony = your personal assistant who writes the emails automatically

Mailjet = your ultra-fast delivery service

Symfony = your personal assistant who writes the emails automatically.

Ok but based on what criteria does it write the emails? randomly, according to the people, according to the profiles ?

Now we get into the real functioning. Here's how Symfony decides what to write, simply.

Symfony does not write emails all by itself like magic First super important thing: Symfony is not an artificial intelligence that invents the email text. It's not ChatGPT ! Symfony does what vous programmed it to do.

How does it work concretely ? Imagine that Symfony is your super organized assistant but who only repeats what you taught it. It has letter templates (we call them templates) that you created in advance.

Example of a template that you write in your code :

```
"Hello [FIRSTNAME], thank you for ordering [PRODUCT] on our site !"

```

Then, Symfony will replace the words in brackets with the real data of each person.

Based on what criteria does Symfony decide the content ? Symfony looks in its database (its big address book with all the info) and it chooses according to what you asked it to check.

Criteria #1 : The PERSON'S PROFILE Symfony looks for the info stored about the user :

Their first name → to personalize "Hello Thomas" or "Hello Léa"

Their age → to suggest adapted games

Their city → to give info about an event near them

Their previous purchases → to recommend similar products

Criteria #2 : The action that the person just did This is the most important criterion !

Symfony decides to send an email when an event happens :

Sign-up → Automatic welcome email

Purchase validated → Confirmation email with the list of items

Forgotten password → Email with a link to reset it

Abandoned cart (the site detects that someone filled their cart but didn't pay) → Email like "Hey, you forgot your items !"

Birthday (if the date is in the database) → Email with a promo code

Criteria #3 : The PERSON'S BEHAVIOR (advanced version) With tools like Mailjet, Symfony can even react to what people did BEFORE:

"They didn't open our last 3 emails" → we send them a different email to wake them up

"They clicked on the sneakers but didn't buy" → we send them a special promo on sneakers

Summary with a concrete example Situation : Thomas (16 years old, fan of soccer games) signs up on your site.

You programmed: "When someone signs up, send the welcome email"

Symfony detects: "Alert ! Thomas just signed up !"

Symfony looks in its database: "He likes soccer, he's 16 years old"

Symfony takes the email template you created : "Welcome \[FIRSTNAME\]! Discover our favorite games for \[AGE\] year olds!"

Symfony replaces: "Welcome Thomas! Discover our favorite games for 16 year olds !"

Symfony hands the email to Mailjet which sends it to Thomas

So YOU are the boss! You decide everything: when to send, to whom, and which template to use. Symfony executes your orders at lightning speed.

so mailjet cannot do that itself ? Can Mailjet do it all by itself ? Short answer: YES and NO. This is where it gets interesting !

What Mailjet can do ALL BY ITSELF Mailjet has its own little assistants (without you needing Symfony). It can:

- Send scheduled emails: "Every Monday at 10am, send this newsletter"
- Do simple automations : "When someone subscribes to my list, send them the welcome email"
- Segment according to profiles : "Show this block if the person is a boy, this other one if it's a girl"

How does it do it? Mailjet has a visual interface with "blocks" to click. You don't write code, you drag and drop to create scenarios.

But... the big limits of Mailjet all by itself

- It does NOT know your site: Mailjet doesn't know that a user just bought a soccer jersey on your site. It doesn't see that someone filled a cart. It is blind to what happens elsewhere than on its platform.
- It cannot react instantly: If Thomas signs up on your site at 3am, how would Mailjet know? Someone would have to go on Mailjet to trigger the send.
- It doesn't know all your data: Mailjet has its own small contact list. But it doesn't know Thomas's score on your game, his level, his friends, his complete history on your site.

This is where Symfony becomes essential Symfony is the link between your site and Mailjet.

Your site (with Symfony) sees everything :

- It sees Thomas sign up
- It sees Thomas buy
- It sees Thomas reach level 10
- It sees Thomas lose his password

Each time, Symfony says: "Hey Mailjet, quick send this special email to Thomas !"

The restaurant analogy Mailjet by itself = A meal delivery service. You can tell them: "Deliver a pizza to 15 Lilas Street every Friday evening." But you have to give them the address and tell them what to deliver.

Symfony + Mailjet = A full restaurant. Symfony is the chef in the kitchen and the waiter who takes orders. As soon as a customer orders, the waiter (Symfony) shouts to the kitchen (your code), and the kitchen calls the delivery person (Mailjet) to dispatch the dish immediately.

Why not do everything with Mailjet then ? You could do everything with Mailjet IF :

Your site is very small

You just want to send basic newsletters

You accept having to configure everything manually on their site

But as soon as your site becomes a bit serious (automatic sign-ups, e-commerce, games with scores...), you really need Symfony to be the conductor.

In short : Mailjet is a great delivery person. But it's Symfony who knows when to call the delivery person and WHAT to give them to deliver.

Can you send emails in Symfony with Mailjet ?
---------------------------------------------

[](#can-you-send-emails-in-symfony-with-mailjet-)

Yes, absolutely. And it's even one of the main uses for which Mailjet is designed.

On their site, they say it's "built for devs" and that you can "Integrate with our API in minutes and start sending". An API is a language that allows your Symfony code (PHP) to talk directly to Mailjet's servers to tell it to send an email.

It's not Mailjet "or" Symfony, it's Mailjet "and" Symfony working together.

Mailjet by itself vs. Mailjet + Symfony: which to choose ? Imagine that Mailjet is an ultra-modern factory that prints and ships letters (emails) to millions of addresses. It has super fast machines, quality paper, and it knows exactly which letter was sent, read, or thrown away.

If you use Mailjet by itself (without Symfony) : It's like going to that factory yourself with a list of names and addresses on a piece of paper. You use their on-site computer to type each letter one by one (via their "drag-and-drop" interface for newsletters). It's perfect for one-off marketing campaigns that you prepare by hand, like a monthly newsletter for your video game club.

If you combine Symfony and Mailjet : Then, it's your website (built with Symfony) that becomes the conductor. It tells the Mailjet factory: "When a new user signs up, automatically send them this welcome email" or "When an order is validated, send the confirmation". Symfony handles the logic and the triggering (WHEN and WHY to send), and Mailjet handles the ultra-fast shipping and tracking (HOW to send it and knowing if it arrived properly).

Why is it better to combine them ? It's automated and reliable: You don't need to go to the Mailjet site at 3 a.m. to send 100 order confirmation emails. Symfony does it by itself, and Mailjet ensures they arrive quickly.

It's more powerful: Symfony can send personalized emails with data from your database (e.g., "Congratulations \[Player Name\] for your score of \[Level\]!"). It's much stronger than writing them all by hand in Mailjet.

You keep Mailjet's power: Even by going through Symfony, you benefit from all of Mailjet's great tools: statistics (who opened the email), deliverability (not in spam), and contact management.

Why would it not be better (or simpler) ? You need to know how to code: For Symfony to talk to Mailjet, you have to write PHP code, install libraries, and manage errors. It's a bit more work at the beginning.

For very simple sends: If you just need to send a newsletter to your list from time to time, directly using Mailjet's "drag-and-drop" editor is much simpler and faster. You don't need a developer for that. The Mailjet site is made for that, with templates and tools for "marketers".

In summary : The Symfony + Mailjet combination is the Formula 1 for websites that need to send emails automatically, in a personalized way, and on a large scale. Using Mailjet by itself is the practical scooter for creating nice newsletters without the headache. The choice depends on what you want to build !

---

Demo Page
---------

[](#demo-page)

**[View the live demo &amp; documentation](https://madjeek-web.github.io/symfony-mailjet-bundle/demo/)**

[![symfony mailjet bundle page demo](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/screen_demo_mailjet.jpg)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/screen_demo_mailjet.jpg)

---

Table of Contents
-----------------

[](#table-of-contents)

1. [What is Mailjet?](#-what-is-mailjet)
2. [What is this project?](#-what-is-this-project)
3. [Why is this project relevant in 2026?](#-why-is-this-project-relevant-in-2026)
4. [Requirements](#-requirements)
5. [Installation](#-installation)
6. [Configuration](#-configuration)
7. [Usage — Sending Emails](#-usage--sending-emails)
8. [Asynchronous Sending with Symfony Messenger](#-asynchronous-sending)
9. [Receiving Webhook Events](#-receiving-webhook-events)
10. [Running the Tests](#-running-the-tests)
11. [Project Architecture (for developers)](#-project-architecture)
12. [For Teachers &amp; Students](#-for-teachers--students)
13. [Contributing](#-contributing)
14. [License](#-license)
15. [Author](#-author)

---

[![mailjet bundle full img](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/mailjet-full-logo-png.png)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/mailjet-full-logo-png.png)

What is Mailjet ?
-----------------

[](#what-is-mailjet-)

Imagine you've built a website where users can create an account. When they sign up, you want to send them a **welcome email**. Or maybe a **"forgot your password"** email with a reset link.

You *could* try to set up your own email server... but that's incredibly complex. You'd need to handle :

- Spam blacklists (big email providers like Gmail might block your emails)
- Server maintenance and configuration
- Delivery tracking and bounce handling
- Security (SPF, DKIM, DMARC records)

This is where **Mailjet** comes in. It's a **SaaS (Software as a Service)** platform that handles ALL of that for you. You just call their API (a simple HTTP request), and they take care of delivering your email reliably to inboxes all over the world.

### Mailjet at a glance

[](#mailjet-at-a-glance)

FeatureDetails**Website****API Docs****Free tier?**Yes! 200 emails/day free, 6,000/month**Paid plans**From ~€15/month for 15,000 emails**Founded**2010, headquartered in Paris, France 🇫🇷**Who uses it?**Over 150,000 companies worldwide### What can Mailjet do ?

[](#what-can-mailjet-do-)

- Transactional emails : Welcome emails, password resets, order confirmations, invoices - triggered by your app
- Marketing emails : Newsletters, promotions, campaigns - sent to many recipients
- Real-time tracking : See who opened your email, who clicked a link, who unsubscribed
- Contact management : Store and manage your mailing lists
- Webhook notifications : Mailjet tells YOUR server when an email bounces, is opened, etc.

---

### What is the concrete purpose of it ?

[](#what-is-the-concrete-purpose-of-it-)

- Send confirmation emails of registration
- Send transactional emails (invoice, reset password, notification)
- Send one-by-one personalized campaigns with a Twig template per recipient
- All without managing a mail server, just an API key

### + + +

[](#--)

Mailjet is a comprehensive platform that combines sending marketing and transactional emails, designed to be used by both developers and marketing teams.

Here is what it allows you to do concretely:

Create professional emails easily Thanks to its drag-and-drop editor, you can design responsive emails, forms, and landing pages, even without technical skills. You start from a blank page, a pre-designed template, or use AI to generate templates and write content adapted to your brand (tone, length, language).

Manage and collaborate as a team The platform includes collaboration tools to create emails with multiple people, with permission systems to protect elements of your brand guidelines. You can start alone and then invite colleagues as needed.

Personalize and automate sends You can segment your contacts by interests, send personalized messages (e.g., different offers based on recipient behavior) and automate journeys like onboarding or re-engagement, via a visual builder.

Improve deliverability So that your emails actually arrive in the inbox (and not in spam), Mailjet offers address validation tools upstream, a preview of email rendering, and real-time performance tracking (open rates, clicks, etc.) with filtering of non-human interactions.

Benefit from expert support at scale For high sending volumes, a dedicated expert supports you with configuration, best practices, and problem resolution, with personalized follow-up.

Integrate with your other tools The platform easily connects to your other software (CRM, CMS, e-commerce) via preconfigured integrations or its API, which developers can use to program automated sends.

Optimize with AI AI assistants help generate custom templates, adapt the tone of messages (informal or formal) in 21 languages, and suggest text lengths to improve performance.

In summary, Mailjet is a complete service that manages the entire email chain: from visual creation to technical deliverability, through marketing automation and results analysis. It is aimed as much at marketing teams as at developers who, as with the Symfony bundle, use it to send emails programmatically.

---

### Mailjet vs competitors

[](#mailjet-vs-competitors)

ServiceFree TierNotes**Mailjet**200/day, 6k/monthEuropean company, GDPR-friendly**SendGrid**100/dayPopular, owned by Twilio**Brevo (Sendinblue)**300/dayFrench company, very complete**Postmark**100/month trialFocused on transactional**Amazon SES**62k/month (if on AWS)Cheapest at scale, complex setup---

[![symfony mailjet black cover img 01](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/symfony_01.png)](https://github.com/madjeek-web/symfony-mailjet-bundle/raw/main/symfony_01.png)

What is this project ?
----------------------

[](#what-is-this-project-)

This is a **Symfony Bundle** - a reusable plugin for PHP applications built with the [Symfony framework](https://symfony.com/).

all the modern best practices available in 2026

### What this bundle does

[](#what-this-bundle-does)

1. **Provides a fluent PHP interface** to build and send emails using the Mailjet API v3.1
2. **Integrates deeply with Symfony 7** (Dependency Injection, Messenger, Events, HTTP Client)
3. **Supports async sending** via Symfony Messenger (your app doesn't wait for the email to be sent)
4. **Receives real-time events** from Mailjet via webhooks (bounces, opens, clicks)
5. **Is ultra-secure** with proper credential handling and webhook signature verification

---

Why is this project relevant in 2026 ?
--------------------------------------

[](#why-is-this-project-relevant-in-2026-)

Here's why this matters right now :

### 1. PHP is booming

[](#1-php-is-booming)

PHP powers **~78% of websites** with a server-side language (including WordPress, Laravel apps, Symfony apps). The latest PHP 8.3 is fast, modern, and has features comparable to other languages. PHP 8.4 is already out.

### 2. Email is still the #1 communication channel

[](#2-email-is-still-the-1-communication-channel)

Despite Slack, Discord, and other messaging apps, **email remains the primary way businesses communicate with customers**. Every app needs to send emails. This bundle makes that easy.

### 3. Symfony 7 is widely used in enterprise PHP

[](#3-symfony-7-is-widely-used-in-enterprise-php)

Companies like Spotify, Trivago, and thousands of enterprises use Symfony as their PHP framework. Knowing how to build quality Symfony bundles is a valuable professional skill.

### 4. Async programming is now essential

[](#4-async-programming-is-now-essential)

Modern applications need to be fast. Users expect sub-100ms responses. Sending emails synchronously (blocking the HTTP response while waiting for an API) is bad practice. This bundle shows how to do it right with Symfony Messenger.

### 5. Code quality matters more than ever

[](#5-code-quality-matters-more-than-ever)

With AI-assisted coding becoming common, the ability to write **tested, typed, maintainable code** that humans AND tools can understand is increasingly valuable. This project demonstrates all of that.

---

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

[](#requirements)

RequirementMinimum VersionNotesPHP8.3Uses readonly classes, enums, named argumentsSymfony Framework7.0Full Symfony 7 integrationSymfony HttpClient7.0For async HTTP requestsSymfony Messenger7.0For async email queuingMailjet AccountFree tier worksGet one at [mailjet.com](https://www.mailjet.com)---

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

[](#installation)

### Step 1 : Install via Composer

[](#step-1--install-via-composer)

[Composer](https://getcomposer.org/) is the dependency manager for PHP. It's like `npm` for JavaScript or `pip` for Python.

```
composer require madjeek-web/symfony-mailjet-bundle
```

If you're using **Symfony Flex** (included by default in new Symfony projects), the bundle will be automatically registered. If not, add it manually to `config/bundles.php`:

```
// config/bundles.php
return [
    // ... other bundles ...
    Madjeek-web\MailjetBundle\MailjetBundle::class => ['all' => true],
];
```

### Step 2 : Get Your Mailjet API Keys

[](#step-2--get-your-mailjet-api-keys)

1. Go to  and create a **free account**
2. Navigate to **Account Settings** → **API Keys**:
3. Copy your **API Key** and **Secret Key**

> **Security tip**: Treat your API keys like passwords. Never commit them to Git. Never hardcode them in PHP files.

### Step 3: Add Credentials to `.env`

[](#step-3-add-credentials-to-env)

Open your project's `.env` file and add:

```
###> madjeek-web/symfony-mailjet-bundle ###
MAILJET_API_KEY=your_api_key_here
MAILJET_SECRET_KEY=your_secret_key_here

# IMPORTANT: Keep true in dev/staging, set to false in production!
# When true, emails are validated but NOT actually sent
MAILJET_SANDBOX_MODE=true

# Optional: generate with: php -r "echo bin2hex(random_bytes(32));"
MAILJET_WEBHOOK_SECRET=
###< madjeek-web/symfony-mailjet-bundle ###
```

> Make sure `.env` is in your `.gitignore` file! Never push real API keys to GitHub.

### Step 4 : Create Bundle Configuration

[](#step-4--create-bundle-configuration)

Create the file `config/packages/mailjet.yaml`:

```
# config/packages/mailjet.yaml
mailjet:
    api_key: '%env(MAILJET_API_KEY)%'
    secret_key: '%env(MAILJET_SECRET_KEY)%'
    sandbox_mode: '%env(bool:MAILJET_SANDBOX_MODE)%'
    webhook_secret: '%env(MAILJET_WEBHOOK_SECRET)%'

    # Optional: set a default sender for all emails
    default_from:
        email: 'noreply@yourapp.com'
        name: 'Your Application Name'
```

That's it !

---

Usage - Sending Emails
----------------------

[](#usage---sending-emails)

### The Basics: EmailMessage Builder

[](#the-basics-emailmessage-builder)

The `EmailMessage` class uses a **fluent builder pattern**. You chain method calls to build up your email, then send it.

```
use Madjeek\MailjetBundle\DTO\EmailMessage;

$email = EmailMessage::create()          // Start building
    ->from('sender@example.com', 'My App')  // Who sends it
    ->to('user@example.com', 'John Doe')    // Main recipient
    ->cc('boss@example.com')                // Carbon copy (optional)
    ->bcc('archive@example.com')            // Blind copy (optional)
    ->replyTo('support@example.com')        // Where replies go (optional)
    ->subject('Your Order Confirmation')    // Subject line
    ->htmlBody('Thank you!Your order #123 is confirmed.')  // HTML version
    ->textBody('Thank you! Your order #123 is confirmed.');  // Plain text fallback
```

### Sending Immediately (Synchronous)

[](#sending-immediately-synchronous)

```
use Madjeek-web\MailjetBundle\Contract\EmailSenderInterface;
use Madjeek-web\MailjetBundle\DTO\EmailMessage;
use Symfony\Component\HttpFoundation\Response;

class PasswordResetController
{
    public function __construct(
        // Symfony's Dependency Injection will automatically provide this!
        private readonly EmailSenderInterface $emailSender
    ) {}

    public function requestReset(string $userEmail): Response
    {
        $resetToken = 'abc123'; // Your actual reset token logic here

        $email = EmailMessage::create()
            ->from('noreply@myapp.com', 'My App Security')
            ->to($userEmail)
            ->subject('Reset your password')
            ->htmlBody(
                'Click here to reset: Reset Password'
            )
            ->textBody('Reset link: https://myapp.com/reset/' . $resetToken);

        // sendNow() blocks until the email is sent — use for critical emails
        $this->emailSender->sendNow($email);

        return new Response('Reset email sent!');
    }
}
```

### Sending with File Attachments

[](#sending-with-file-attachments)

```
$email = EmailMessage::create()
    ->from('billing@myapp.com', 'Billing Department')
    ->to('customer@example.com', 'Valued Customer')
    ->subject('Invoice #2026-042')
    ->htmlBody('Please find your invoice attached.')
    ->textBody('Please find your invoice attached.')

    // Option A: Attach from file path (most common)
    ->attachFile('/var/www/storage/invoices/invoice-042.pdf')

    // Option B: Attach with a custom display name
    ->attachFile('/tmp/report.xlsx', 'Q1 Financial Report.xlsx')

    // Option C: Attach raw binary content (e.g. generated PDF in memory)
    ->attach('receipt.pdf', $pdfBinaryContent, 'application/pdf');

$this->emailSender->sendNow($email);
```

### Adding Tracking Variables

[](#adding-tracking-variables)

Mailjet lets you attach custom variables to emails for tracking in their dashboard :

```
$email = EmailMessage::create()
    ->from('noreply@myapp.com')
    ->to('user@example.com')
    ->subject('Your Order is Shipped!')
    ->htmlBody('Your package is on the way!')
    ->withVariable('order_id', '12345')      // Track by order
    ->withVariable('user_segment', 'premium') // Track by user type
    ->withVariable('campaign', 'summer-2026'); // Track by campaign
```

These variables appear in Mailjet's statistics dashboard, letting you analyze email performance by segment.

---

Asynchronous Sending
--------------------

[](#asynchronous-sending)

### Why Async ?

[](#why-async-)

Consider this scenario: A user submits your registration form. Your server needs to :

1. Validate the form data
2. Create a user record in the database
3. Send a welcome email via Mailjet API
4. Return an HTTP response to the user

If Mailjet's API takes 500ms to respond (which is normal for network calls), your user waits 500ms+ just for the email. That's bad UX.

**With async sending:**

- Steps 1, 2, and 4 happen in your normal HTTP request (~10ms total)
- Step 3 is **queued** - a background worker processes it a moment later
- Your user gets an instant response
- The email arrives in their inbox within seconds

### Setting Up Async

[](#setting-up-async)

First, make sure Symfony Messenger is installed :

```
composer require symfony/messenger
```

Configure a transport in `config/packages/messenger.yaml`:

```
framework:
    messenger:
        transports:
            # Use Redis for production (fast, reliable)
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                options:
                    auto_setup: true

        routing:
            # Route our email messages to the async transport
            'Madjeek-web\MailjetBundle\Message\SendEmailMessage': async
```

Add to your `.env`:

```
# Use Redis (recommended for production):
MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages

# Or use database (simpler, no Redis needed):
# MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=1
```

Now use `sendAsync()` in your code :

```
// This returns INSTANTLY — email is queued!
$this->emailSender->sendAsync($email);
```

Start the worker (run in a separate terminal or via Supervisor) :

```
# Process messages for 1 hour, then restart (good for memory management)
php bin/console messenger:consume async --time-limit=3600

# Or with verbose output (useful for debugging):
php bin/console messenger:consume async -vv
```

### For Production : Use Supervisor

[](#for-production--use-supervisor)

[Supervisor](http://supervisord.org/) keeps your worker running even if it crashes :

```
# /etc/supervisor/conf.d/messenger.conf
[program:symfony-messenger]
command=php /var/www/html/bin/console messenger:consume async --time-limit=3600
user=www-data
numprocs=2
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/messenger-error.log
stdout_logfile=/var/log/supervisor/messenger.log
```

---

Receiving Webhook Events
------------------------

[](#receiving-webhook-events)

### What is a Webhook ?

[](#what-is-a-webhook-)

A webhook is the **reverse** of a normal API call :

- **Normal API** : your app → sends a request → Mailjet responds
- **Webhook** : Mailjet → sends a request → your app receives it

When something happens to your emails (delivered, bounced, opened, link clicked, unsubscribed, marked as spam), Mailjet sends a **POST request** to a URL you configure. This lets you react in real time.

### Configure Webhooks in Mailjet

[](#configure-webhooks-in-mailjet)

1. Log in to [app.mailjet.com](https://app.mailjet.com)
2. Go to **Account Settings** → **Event Notifications / Triggers**
3. Add your webhook URL: `https://yourdomain.com/mailjet/webhook`
4. Select which events to receive (delivered, open, click, bounce, spam, unsub)

> Your webhook URL must be **publicly accessible** (Mailjet needs to reach it). In local development, use [ngrok](https://ngrok.com/) to create a temporary public URL.

### Add the Webhook Route

[](#add-the-webhook-route)

The bundle provides a controller. Add the route to your app's routing config :

```
# config/routes/mailjet.yaml
mailjet_webhook:
    resource: '@MailjetBundle/src/Webhook/WebhookController.php'
    type: attribute
```

### Listen to Webhook Events in Your App

[](#listen-to-webhook-events-in-your-app)

You can extend the webhook controller's behavior by listening to Symfony events that it dispatches, or by customizing the `processEvent()` method in your own controller that extends it.

For simple use cases, the built-in controller logs all events automatically. For production, add your own event listener:

```
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

// This is YOUR custom code, not part of the bundle
#[AsEventListener('mailjet.email_sent')]
class HandleEmailSentListener
{
    public function __invoke(EmailSentEvent $event): void
    {
        $email = $event->getEmail();
        $messageIds = $event->getMailjetMessageIds();

        // Example: save message IDs to database for delivery tracking
        // $this->emailRepository->updateWithMessageIds($email, $messageIds);
    }
}
```

---

Running the Tests
-----------------

[](#running-the-tests)

### Install test dependencies

[](#install-test-dependencies)

```
composer install  # Installs everything including dev dependencies
```

### Run all tests

[](#run-all-tests)

```
composer test
# Equivalent to: vendor/bin/phpunit
```

### Run with coverage report (HTML)

[](#run-with-coverage-report-html)

```
vendor/bin/phpunit --coverage-html coverage/
# Open coverage/index.html in your browser to see coverage
```

### Run a specific test file

[](#run-a-specific-test-file)

```
vendor/bin/phpunit tests/Unit/DTO/EmailAddressTest.php
```

### Run static analysis

[](#run-static-analysis)

```
composer stan
# Equivalent to: vendor/bin/phpstan analyse --level=9
```

### Check code style

[](#check-code-style)

```
composer cs-check   # Just check (no changes)
composer cs-fix     # Auto-fix all style issues
```

### Run everything at once

[](#run-everything-at-once)

```
composer quality
# Runs: cs-check → stan → tests
```

---

Project Architecture
--------------------

[](#project-architecture)

Here's how the code is organized, and why :

```
symfony-mailjet-bundle/
│
├── src/
│   ├── MailjetBundle.php           ← Entry point: tells Symfony this bundle exists
│   │
│   ├── Contract/                   ← Interfaces (the "contracts")
│   │   ├── MailjetClientInterface.php   ← Contract for the HTTP client
│   │   └── EmailSenderInterface.php     ← Contract for the high-level sender
│   │
│   ├── DTO/                        ← Data Transfer Objects (typed data containers)
│   │   ├── EmailAddress.php        ← Validated email address (readonly, immutable)
│   │   ├── Attachment.php          ← File attachment with size validation
│   │   └── EmailMessage.php        ← The main email builder (fluent API)
│   │
│   ├── Exception/                  ← Custom exceptions for clear error handling
│   │   ├── MailjetApiException.php ← API/network errors
│   │   └── InvalidEmailException.php ← Bad input data errors
│   │
│   ├── Http/                       ← The actual HTTP communication layer
│   │   └── MailjetClient.php       ← Calls the Mailjet API via Symfony HttpClient
│   │
│   ├── Message/                    ← Symfony Messenger messages
│   │   └── SendEmailMessage.php    ← A "job" that gets queued for async processing
│   │
│   ├── Handler/                    ← Symfony Messenger handlers
│   │   └── SendEmailMessageHandler.php  ← Processes queued email jobs
│   │
│   ├── Event/                      ← Symfony Events (for extensibility)
│   │   ├── EmailSentEvent.php      ← Fired after successful send
│   │   └── EmailFailedEvent.php    ← Fired after failed send
│   │
│   ├── Service/                    ← High-level business logic
│   │   └── MailjetEmailSender.php  ← The service you inject in YOUR code
│   │
│   ├── Webhook/                    ← Handling events FROM Mailjet
│   │   └── WebhookController.php   ← Receives and verifies webhook POST requests
│   │
│   └── DependencyInjection/        ← Symfony integration
│       ├── Configuration.php       ← Defines mailjet.yaml config structure
│       └── MailjetExtension.php    ← Registers all services in Symfony's DI container
│
├── tests/
│   ├── Unit/                       ← Tests that test one class in isolation
│   │   ├── DTO/
│   │   │   ├── EmailAddressTest.php
│   │   │   └── EmailMessageTest.php
│   │   └── Http/
│   │       └── MailjetClientTest.php
│   └── Integration/                ← Tests that test multiple classes together
│
├── config/
│   └── services.yaml               ← Example configuration
│
├── demo/
│   └── index.html                  ← GitHub Pages demo site
│
├── .github/
│   ├── workflows/
│   │   └── ci.yml                  ← GitHub Actions: auto-runs tests on push
│   ├── CONTRIBUTING.md
│   └── ISSUE_TEMPLATE/
│
├── composer.json                   ← PHP package definition (like package.json)
├── phpunit.xml                     ← Test runner configuration
├── phpstan.neon                    ← Static analysis configuration
├── .php-cs-fixer.php               ← Code style configuration
├── .gitignore
├── README.md                       ← This file!
└── SECURITY.md                     ← Security policy

```

### Design Patterns Used

[](#design-patterns-used)

PatternWhereWhy**Builder**`EmailMessage`Fluent API for building complex objects**Value Object**`EmailAddress`, `Attachment`Immutable, self-validating data**Dependency Injection**All servicesDecouples classes, enables testing**Interface/Contract**`MailjetClientInterface`, `EmailSenderInterface`Swappable implementations**Command/Message**`SendEmailMessage`Enables async processing**Observer/Event**`EmailSentEvent`, `EmailFailedEvent`Extensibility without coupling---

For Teachers &amp; Students
---------------------------

[](#for-teachers--students)

### Learning Objectives

[](#learning-objectives)

This project demonstrates several important programming concepts :

#### Object-Oriented Programming (OOP)

[](#object-oriented-programming-oop)

- **Classes and Objects**: Every file is a class. `EmailMessage`, `EmailAddress`, `MailjetClient` are all classes.
- **Encapsulation**: Private properties with public methods. You can't directly change `EmailMessage::$from` — you must use `->from()`.
- **Inheritance**: `MailjetApiException extends RuntimeException` — it inherits all exception behavior and adds specific ones.
- **Interfaces**: `EmailSenderInterface` is a contract that multiple classes can implement. Allows swapping implementations.

#### SOLID Principles

[](#solid-principles)

- **S**ingle Responsibility: Each class does ONE thing. `MailjetClient` only handles HTTP. `EmailMessage` only holds email data.
- **O**pen/Closed: You can extend behavior via events without modifying the bundle's source.
- **L**iskov Substitution: `MailjetEmailSender` implements `EmailSenderInterface` — you can swap it for a test fake.
- **I**nterface Segregation: Two separate interfaces for two different concerns (HTTP client vs. high-level sender).
- **D**ependency Inversion: High-level code depends on abstractions (interfaces), not concrete classes.

#### PHP 8.3 Features Used

[](#php-83-features-used)

- `readonly` classes and properties (immutable value objects)
- `declare(strict_types=1)` (strict type checking)
- Named arguments (`new MailjetClient(apiKey: 'x', secretKey: 'y')`)
- Nullsafe operator (`$obj?->method()`)
- Match expressions
- Union types (`string|null`)
- Constructor property promotion

#### Testing Concepts

[](#testing-concepts)

- **Unit tests** with PHPUnit
- **Mocking** with `MockHttpClient` (replace real HTTP with fake responses)
- **Data Providers** for testing multiple inputs
- **AAA pattern** (Arrange, Act, Assert)
- **Test coverage** measurement

### Using This Project in a Course

[](#using-this-project-in-a-course)

This project can be used to teach:

1. **Week 1**: PHP OOP basics using `EmailAddress` as an example of a simple class
2. **Week 2**: Interfaces and dependency injection using the service layer
3. **Week 3**: Testing with PHPUnit / run the existing tests, then write new ones
4. **Week 4**: HTTP APIs / how `MailjetClient` communicates with external services
5. **Week 5**: Async programming / Symfony Messenger, queues, workers
6. **Week 6**: Security / API key management, webhook verification

### Workshop Exercise Ideas

[](#workshop-exercise-ideas)

1. **Add CC/BCC validation** : Verify that BCC recipients aren't also in the TO list
2. **Add Twig integration** : Render a Twig template as the email body
3. **Add email templates** : Support Mailjet's server-side template variables
4. **Build a test listener** : Create an `EmailSentEvent` listener that logs to a database
5. **Add retry logic** : Automatically retry when Mailjet returns a 429 (rate limit) error

---

Contributing
------------

[](#contributing)

Contributions are welcome from whether you're a student learning PHP, a professional developer, or a teacher improving the documentation.

### How to Contribute

[](#how-to-contribute)

1. Fork this repository on GitHub
2. Clone your fork : `git clone https://github.com/YOUR_USERNAME/symfony-mailjet-bundle.git`
3. Create a branch : `git checkout -b feature/my-improvement`
4. Make your changes (with tests!)
5. Run quality checks : `composer quality`
6. Push and open a **Pull Request**

### Good First Issues

[](#good-first-issues)

Look for issues labeled `good first issue` on GitHub - these are small, well-defined tasks perfect for newcomers.

### What We Need

[](#what-we-need)

- Bug reports and fixes
- Documentation improvements
- More test cases
- New features (open an issue first to discuss)
- Translations of comments to other languages

### Code Style

[](#code-style)

We follow the **Symfony Coding Standards**. Run `composer cs-fix` before committing to auto-format your code.

---

License
-------

[](#license)

This project is licensed under the **MIT License** one of the most permissive open-source licenses.

### What MIT means for you :

[](#what-mit-means-for-you-)

You can use this in commercial projects
You can modify the code
You can redistribute it
You can use it privately
The author provides NO warranty
You must include the copyright notice

Full license text :

---

Author
------

[](#author)

**Fabien Conéjéro**

- GitHub: [@Fabien\_Conéjéro](https://github.com/madjeek-web)
- Repository: [github.com/madjeek/symfony-mailjet-bundle](https://github.com//madjeek-web/symfony-mailjet-bundle)

*Created on February 20, 2026*

---

Useful Links
------------

[](#useful-links)

ResourceURLMailjet Official WebsiteMailjet API DocumentationMailjet API KeysSymfony Official WebsiteSymfony Messenger DocsSymfony HttpClient Docs[https://symfony.com/doc/current/http\_client.html](https://symfony.com/doc/current/http_client.html)PHP 8.3 Release NotesComposer (PHP package manager)PHPUnit Testing FrameworkPHPStan Static Analysis---

- If this project helped you, please star it on GitHub ! It encourages continued development.
- My bundle is installable by any Symfony developer in the world with :

```
composer require madjeek-web/symfony-mailjet-bundle
```

- The package is published on Packagist ()
- GitHub webhook configuration for automatic update : Yes
- Creation of the release v1.0.0 : Yes
- The package is published on Packagist :

༄☕︎︎︎ Buy Me A Coffee :
-----------------------

[](#︎︎︎-buy-me-a-coffee-)

[![Buy Me A Coffee image](https://github.com/madjeek-web/eventflow/raw/main/Buy_Me _A_Coffee.jpg)](https://donate.stripe.com/3cI6oH1nUgsy8WZdVHgEg00)

༄☕︎︎︎ [stripe.com](https://donate.stripe.com/3cI6oH1nUgsy8WZdVHgEg00)

. Thank you for your support

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance77

Regular maintenance activity

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

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

132d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/540b8b6f674661bf0e9ddd78d7081cc91f4f83e2b9435d8e4b809f7799754cc1?d=identicon)[madjeek-web](/maintainers/madjeek-web)

---

Top Contributors

[![madjeek-web](https://avatars.githubusercontent.com/u/83957788?v=4)](https://github.com/madjeek-web "madjeek-web (103 commits)")

---

Tags

2026async-readybundleemailfabienfabien-conejerohttp-clientmadjeekmailjetmailjet-apimessengermitmit-licenseopen-sourcephpphp8projectsymfonysymfony-bundlewebhookasyncsymfonybundleemailmailertransactionalMessengerMailjetphp8

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/madjeek-web-symfony-mailjet-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/madjeek-web-symfony-mailjet-bundle/health.svg)](https://phpackages.com/packages/madjeek-web-symfony-mailjet-bundle)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M737](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M204](/packages/sulu-sulu)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M577](/packages/shopware-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

515100.5k3](/packages/web-auth-webauthn-framework)[kimai/kimai

Kimai - Time Tracking

4.8k9.0k1](/packages/kimai-kimai)

PHPackages © 2026

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