# Sendgrid bounce management not working

**URL:** <https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613>\
**Category:** Product Support\
**Created:** [October 4, 2026, 5:59pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613 "2026-10-04T17:59:26Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![adam5532](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/adam5532/32/12643_2.png) [@adam5532](https://forum.mautic.org/u/adam5532)\
**Post date:** [October 4, 2026, 5:59pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/1 "2026-10-04T17:59:26Z")

</div>

**Your software**  
My Mautic version is: 7.2.1  
My PHP version is: 8.4  
My Database type and version is: MariaDB 11.4

**Your problem**  
My problem is: Sendgrid bounce management not working, neither webhook nor monitored box

API:  
Webhook callback configured & enabled in Sendgrid: [https://news.mydomain.org/mailer/callback](https://news.mydomain.org/mailer/callback)

[bounces+bounce\_string@mydomain.org](mailto:bounces+bounce_string@mydomain.org)

Monitored inbox:  
Forward bounce messages configured and enabled in Sendgrid and Mautic. Bounce message reaches monitored inbox. Cron job is running (mautic📧fetch)

a) Don’t know if sendgrid header is incompatible with Mautic. It is:  
X-SendGrid-Sender: [bounces+bounce\_somestring@mydomain.org](mailto:bounces+bounce_somestring@mydomain.org)  
“somestring” is a letter/number string – I don’t know if this is the encoded rejected email address.

b) The email does not show as “read” after the cron job has supposedly run. I don’t know if that indicates anything, but I assume that it would get marked as read so that Mautic doesn’t reprocess all the emails in the inbox every time.

Note: Sending out is using the Sendgrid API and that’s working fine.

I’d like to get one or the other of the bounce management methods working (webhook would be preference). Don’t know what diagnostics there are to troubleshoot this.

Thanks

---

<div class="post-metadata">

**Author:** ![alexzlobin](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/alexzlobin/32/13606_2.png) [@alexzlobin](https://forum.mautic.org/u/alexzlobin)\
**Post date:** [October 4, 2026, 8:21pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/2 "2026-10-04T20:21:58Z")

</div>

Hi Adam,

I developed a Composer-installable SendGrid callback plugin for Mautic, which we are already successfully using in our own production environments:

- GitHub: [GitHub - AlexanderZlobinM1/SendgridCallBackBundle · GitHub](https://github.com/AlexanderZlobinM1/SendgridCallBackBundle)
- Composer package: `azlobin/mautic-sendgrid-callback`

The current version is `1.5.11` and supports Mautic 5, 6, and 7.

The plugin processes SendGrid Event Webhook events such as `bounce`, `blocked`, `dropped`, `spamreport`, and unsubscribe events, and automatically marks the corresponding contacts as Do Not Contact in Mautic.

The plugin is already receiving and processing SendGrid Event Webhook events successfully in our production environments.

For the webhook endpoint, configure SendGrid to use:

```plaintext
https://your-mautic-domain.example/mailer/callback

```

The plugin must be installed, enabled, and configured in Mautic. The required event switches should also be enabled.

The mailer DSN should use either:

- `sendgrid+api://` — preferred
- `sendgrid+smtp://` — legacy

When using the SendGrid API transport, make sure the Symfony SendGrid Mailer component is installed. From the Mautic root directory, run:

```bash
cd {rootMauticFolder}
sudo -u www-data composer require symfony/sendgrid-mailer:\*

```

Please note that the `X-SendGrid-Sender` header is not used to identify the contact. The plugin processes the JSON payload received from the SendGrid Event Webhook, primarily using the `event` and `email` fields.

Monitored mailbox processing is a separate native Mautic mechanism and is not handled by this plugin. It requires properly configured IMAP settings and the `mautic:email:fetch` command.

I would be very happy if you could try the plugin and share your feedback. In particular, it would be useful to know whether it works correctly with your SendGrid configuration and whether any additional diagnostics or compatibility improvements would be helpful.

If your installation does not process the events, I would recommend checking:

- whether the plugin is installed and enabled;
- whether the required event switches are enabled;
- the web server access log for requests to `/mailer/callback`;
- SendGrid’s Event Webhook delivery history.

These checks should help determine exactly where the processing flow stops.

Best regards,

---

<div class="post-metadata">

**Author:** ![adam5532](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/adam5532/32/12643_2.png) [@adam5532](https://forum.mautic.org/u/adam5532)\
**Post date:** [October 5, 2026, 9:08pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/3 "2026-10-05T21:08:22Z")

</div>

Thank you, this is great! However, it isn’t working yet! Bounce test contact bounced in Sendgrid but not marked in Mautic. I don’t see an access entry for /mailer/callback in my server log. Is there anywhere in Sendgrid to see a log of outgoing webhooks? Let me know if you want to troubleshoot in this thread or take this offline.

---

<div class="post-metadata">

**Author:** ![alexzlobin](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/alexzlobin/32/13606_2.png) [@alexzlobin](https://forum.mautic.org/u/alexzlobin)\
**Post date:** [October 6, 2026, 10:09am UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/4 "2026-10-06T10:09:02Z")

</div>

## Recommended Configuration

The following procedure was verified on **Mautic 7.2.1 with PHP 8.4** using the current plugin version **1.5.11**.

Verification results:

- ZIP installation completed successfully.
- Plugin registered with `is_missing = 0`.
- Real database callback test passed.
- Duplicate webhook processing did not create duplicate DNC records.
- HTTP callback returned `200 OK`.
- Response:

```plaintext
SendGrid Callback processed (1)

```

## 1. Download and Install the ZIP

Download the plugin ZIP from GitHub:

[SendgridCallbackBundle releases](https://github.com/AlexanderZlobinM1/SendgridCallBackBundle/releases)

In Mautic:

```plaintext
Settings → Plugins → Install/Upgrade Plugins

```

Upload the ZIP archive and run **Reload Plugins**.

If installing manually, the final directory must be:

```plaintext
<mautic-root>/plugins/SendgridCallbackBundle/

```

The file structure must look like this:

```plaintext
plugins/SendgridCallbackBundle/SendgridCallbackBundle.php
plugins/SendgridCallbackBundle/Config/config.php
plugins/SendgridCallbackBundle/Config/services.php
plugins/SendgridCallbackBundle/EventSubscriber/CallbackSubscriber.php
plugins/SendgridCallbackBundle/Resources/views/Integration/footer.html.twig

```

For the standard ZIP Mautic installation, do not place it under `docroot/plugins`. The correct path is:

```plaintext
<mautic-root>/plugins/SendgridCallbackBundle

```

After copying the plugin manually:

```bash
php bin/console cache:clear --env=prod
php bin/console mautic:plugins:reload

```

## 2. Enable the Plugin Integration

Open:

```plaintext
Settings → Plugins → SendGrid Callback

```

Enable and publish the integration.

Enable these event handlers:

- Handle bounce events
- Handle blocked events
- Handle dropped events
- Handle spam report events
- Handle unsubscribe events
- Handle group unsubscribe events

Recommended dropped-event policy:

```plaintext
Auto:
unsubscribe/spam → Unsubscribed
other dropped events → Bounced

```

The plugin must be published. If the integration is disabled, the callback will not process events.

## 3. Verify the SendGrid Transport

The sending DSN should use the SendGrid API transport:

```plaintext
sendgrid+api://API_KEY@default

```

The legacy SMTP transport is also recognized:

```plaintext
sendgrid+smtp://USERNAME:PASSWORD@default

```

If API sending already works correctly, do not change the existing mailer configuration.

The plugin does not send messages itself. It only processes the SendGrid Event Webhook callback.

## 4. Configure SendGrid Event Webhook

In SendGrid:

```plaintext
Settings → Mail Settings → Event Webhook

```

Set the POST URL to:

```plaintext
https://news.mydomain.org/mailer/callback

```

Enable the webhook.

Select these events:

- Bounces
- Dropped
- Spam Reports
- Unsubscribes
- Group Unsubscribes

SendGrid sends event data as a JSON array. A bounce event has the following general structure:

```json
[
  {
    "email": "contact@example.com",
    "event": "bounce",
    "reason": "550 unknown recipient",
    "status": "5.1.1",
    "type": "bounce"
  }
]

```

SendGrid documents `email` as the recipient address and `event` as the event type. [SendGrid Event Webhook Reference](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event)

A blocked bounce is normally represented by:

```json
{
  "event": "bounce",
  "type": "blocked"
}

```

The plugin recognizes this and applies the blocked-event setting.

## 5. Test the Endpoint with cURL

Use an email address that already exists as a contact in Mautic:

```bash
curl -i -X POST 'https://news.mydomain.org/mailer/callback' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: SendGrid Event API' \
  --data '[{
    "event": "bounce",
    "email": "existing-contact@example.com",
    "reason": "mailbox not found",
    "status": "5.1.1",
    "type": "bounce"
  }]'

```

Expected response:

```plaintext
HTTP/2 200

```

Response body:

```plaintext
SendGrid Callback processed (1)

```

The contact should receive the Mautic DNC status:

```plaintext
Bounced

```

Spam report test:

```bash
curl -i -X POST 'https://news.mydomain.org/mailer/callback' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: SendGrid Event API' \
  --data '[{
    "event": "spamreport",
    "email": "existing-contact@example.com"
  }]'

```

Expected contact status:

```plaintext
Unsubscribed

```

Dropped event test:

```bash
curl -i -X POST 'https://news.mydomain.org/mailer/callback' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: SendGrid Event API' \
  --data '[{
    "event": "dropped",
    "email": "existing-contact@example.com",
    "reason": "Bounced Address"
  }]'

```

The result depends on the configured dropped-event policy.

## 6. SendGrid Test Button

SendGrid has a **Test Your Integration** button. It sends a POST request with sample events and sample data, not necessarily a real event from an actual contact. Therefore, this test verifies:

- URL availability;
- HTTPS connectivity;
- POST method;
- JSON format;
- Mautic HTTP response.

It does not necessarily update a Mautic contact.

SendGrid confirms that the integration test uses example events. [SendGrid Event Webhook Overview](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/twilio-sendgrid-event-webhook-overview)

For a real DNC verification, use the cURL request above with an existing Mautic contact.

## 7. Logs to Check

### Nginx access log

```bash
grep '/mailer/callback' /var/log/nginx/access.log

```

Or:

```bash
grep '/mailer/callback' /var/log/nginx/*access*.log

```

A valid request should contain:

```plaintext
POST /mailer/callback

```

Interpretation:

```plaintext
200 Plugin processed the event
404 Plugin/integration is not active or callback handler is unavailable
403 Firewall, authentication or access rule blocked the request
301/302 Redirect was returned instead of processing the POST
500 PHP or Mautic application error

```

### Mautic log

From the Mautic root:

```bash
tail -f var/logs/mautic_prod.php

```

Depending on the Mautic log configuration:

```bash
tail -f var/logs/mautic_prod-*.php

```

Useful plugin messages include:

```plaintext
Processed SendGrid bounce for ...
Processed SendGrid dropped for ...
Processed SendGrid unsubscribe for ...

```

If the email cannot be matched:

```plaintext
Provider feedback lacks a valid Mautic email ID; applying contact-level DNC only.

```

This is not necessarily an error. The contact can still be marked DNC by email address.

## 8. Meaning of the 404 Error

If the response is:

```plaintext
No email transport that could process this callback was found

```

check these items in order:

1. `SendgridCallbackBundle` is present under `plugins/SendgridCallbackBundle`.
2. The plugin appears in Mautic’s plugin list.
3. The plugin integration is published.
4. The Mautic cache was cleared.
5. `mautic:plugins:reload` was executed.
6. The DSN uses `sendgrid+api://` or `sendgrid+smtp://`.
7. The webhook URL is exactly:

```plaintext
https://news.mydomain.org/mailer/callback

```

1. SendGrid is sending a `POST` request with `Content-Type: application/json`.

The most common cause is that the plugin is installed but its integration remains disabled, or the cache was not rebuilt after installation.

## Supported Events

| SendGrid event | Mautic result |
| --- | --- |
| `bounce` | `Bounced` |
| `bounce` with `type: blocked` | `Bounced` |
| `dropped` | Based on dropped-event policy |
| `spamreport` | `Unsubscribed` |
| `unsubscribe` | `Unsubscribed` |
| `group_unsubscribe` | `Unsubscribed` |

The plugin does not process ordinary delivery, open or click events because those events do not represent a DNC change.

SendGrid also supports signed webhook requests using `X-Twilio-Email-Event-Webhook-Signature` and `X-Twilio-Email-Event-Webhook-Timestamp`. [SendGrid Webhook Security](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/getting-started-event-webhook-security-features)

---

<div class="post-metadata">

**Author:** ![adam5532](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/adam5532/32/12643_2.png) [@adam5532](https://forum.mautic.org/u/adam5532)\
**Post date:** [October 8, 2026, 6:29pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/5 "2026-10-08T18:29:04Z")

</div>

Thanks, I will troubleshoot and report back. A couple of notes:

1. The composer installer installs 1.5.10, not 1.5.11.
2. Blocked, or Bounced (with type blocked) are not shown as options in my Sendgrid eventhook settings.

 ![2026-10-08_11-27-25](https://us1.discourse-cdn.com/flex020/uploads/mautic/original/2X/0/0fd326ab3238e3e2628282cf2e4682f00593acbc.jpeg)

---

<div class="post-metadata">

**Author:** ![adam5532](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/adam5532/32/12643_2.png) [@adam5532](https://forum.mautic.org/u/adam5532)\
**Post date:** [October 9, 2026, 12:14am UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/6 "2026-10-09T00:14:55Z")

</div>

The cURL tests work perfectly. Sending a webhook from SendGrid to a test site works properly. However, I can not find any sign of an incoming connection to /mailer/callback in the server access logs, or anything blocked. I’m guessing that it might be a firewall block at my web host (I’m on a VPS running WHM/cPanel). I could see if they can monitor that. There isn’t a fixed IP from SendGrid. Would setting up the security features on the SendGrid webhook help get it through? If so, how would I configure that?

---

<div class="post-metadata">

**Author:** ![alexzlobin](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/alexzlobin/32/13606_2.png) [@alexzlobin](https://forum.mautic.org/u/alexzlobin)\
**Post date:** [October 9, 2026, 10:24am UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/7 "2026-10-09T10:24:01Z")

</div>

Hi Adam,

Thanks for checking this.

You are correct about both points.

### Composer version

Packagist currently contains `v1.5.11`. If Composer installs `1.5.10`, it is most likely using the existing `composer.lock` file or a cached package.

Please update it explicitly from the Mautic root directory:

```bash
composer clear-cache
composer require azlobin/mautic-sendgrid-callback:^1.5 --with-all-dependencies

```

Then verify:

```bash
composer show azlobin/mautic-sendgrid-callback

```

It should show:

```plaintext
versions : * 1.5.11

```

For an already installed package, use:

```bash
composer update azlobin/mautic-sendgrid-callback --with-all-dependencies

```

After updating:

```bash
php bin/console mautic:plugins:reload
php bin/console cache:clear --env=prod

```

The installation instructions have been updated accordingly.

### Blocked events in SendGrid

You are also correct that SendGrid does not show a separate **Blocked** checkbox.

Blocked delivery events are sent through:

```plaintext
Deliverability Data → Bounced

```

They appear in the webhook payload like this:

```json
[
  {
    "email": "contact@example.com",
    "event": "bounce",
    "type": "blocked",
    "reason": "temporary delivery failure",
    "status": "4.7.0"
  }
]

```

So selecting **Bounced** in SendGrid is correct. There is no separate SendGrid option called **Blocked**.

The plugin handles this payload separately:

- `event=bounce`, `type=bounce` → permanent bounce;
- `event=bounce`, `type=blocked` → blocked event.

The plugin’s own **Handle blocked events** switch controls the second case.

The recommended SendGrid event selections are:

- Bounced;
- Dropped;
- Spam Reports;
- Unsubscribed;
- Group Unsubscribes.

Regarding the missing requests in your server logs: because cURL works and SendGrid reaches an external test endpoint, but no request appears on the VPS, the remaining issue is probably before Mautic: DNS, Cloudflare, WHM/cPanel firewall, CSF/LFD, ModSecurity, Imunify360, Nginx/Apache routing, or the hosting provider’s firewall.

SendGrid does not have one permanent source IP that should be allowlisted. Signed Event Webhook or OAuth will not make a blocked request pass through the firewall; those features only authenticate a request after it reaches the server.

The hosting provider should monitor the server while clicking **Test Integration** in SendGrid:

```bash
tcpdump -ni any tcp port 443

```

and check the domain, SSL, ModSecurity and firewall logs. The expected application response after the request reaches Mautic is:

```plaintext
HTTP 200
SendGrid Callback processed (1)

```

---

<div class="post-metadata">

**Author:** ![adam5532](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/adam5532/32/12643_2.png) [@adam5532](https://forum.mautic.org/u/adam5532)\
**Post date:** [October 9, 2026, 10:59pm UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/8 "2026-10-09T22:59:48Z")

</div>

Working perfectly!!! (I found a typo at my end in the callback URL after checking it many times)

I tried to update again using

composer update azlobin/mautic-sendgrid-callback --with-all-dependencies

and it still will not update my 1.5.10 installation to 1.5.11.

---

<div class="post-metadata">

**Author:** ![alexzlobin](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.mautic.org/alexzlobin/32/13606_2.png) [@alexzlobin](https://forum.mautic.org/u/alexzlobin)\
**Post date:** [October 10, 2026, 8:50am UTC](https://forum.mautic.org/t/sendgrid-bounce-management-not-working/38613/9 "2026-10-10T08:50:08Z")

</div>

Hi Adam

Thanks for confirming that the webhook is working.

I tested this update scenario on a clean Mautic 7.2.1 / PHP 8.4 environment. The plugin and version `1.5.11` work correctly.

The reason Composer remains on `1.5.10` is that `composer update` cannot override an exact version constraint such as:

```auto
"azlobin/mautic-sendgrid-callback": "1.5.10"

```

Please check the requirement in your project:

```auto
composer show azlobin/mautic-sendgrid-callback --direct

```

Then update it with:

```auto
composer require azlobin/mautic-sendgrid-callback:^1.5 --with-all-dependencies

```

Or install the exact release:

```auto
composer require azlobin/mautic-sendgrid-callback:1.5.11 --with-all-dependencies

```

After that, verify the installed versions:

```auto
composer show azlobin/mautic-sendgrid-callback
composer show symfony/sendgrid-mailer

```

The expected result is:

```auto
azlobin/mautic-sendgrid-callback 1.5.11
symfony/sendgrid-mailer 7.4.x

```

No plugin code change is required. The update is controlled by the version constraint in the root `composer.json`.
