Sendgrid bounce management not working

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

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:email:fetch)

a) Don’t know if sendgrid header is incompatible with Mautic. It is:
X-SendGrid-Sender: 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

Hi Adam,

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

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:

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:

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,

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.

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:
SendGrid Callback processed (1)

1. Download and Install the ZIP

Download the plugin ZIP from GitHub:

SendgridCallbackBundle releases

In Mautic:

Settings → Plugins → Install/Upgrade Plugins

Upload the ZIP archive and run Reload Plugins.

If installing manually, the final directory must be:

<mautic-root>/plugins/SendgridCallbackBundle/

The file structure must look like this:

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:

<mautic-root>/plugins/SendgridCallbackBundle

After copying the plugin manually:

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

2. Enable the Plugin Integration

Open:

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:

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:

sendgrid+api://API_KEY@default

The legacy SMTP transport is also recognized:

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:

Settings → Mail Settings → Event Webhook

Set the POST URL to:

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:

[
  {
    "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

A blocked bounce is normally represented by:

{
  "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:

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:

HTTP/2 200

Response body:

SendGrid Callback processed (1)

The contact should receive the Mautic DNC status:

Bounced

Spam report test:

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:

Unsubscribed

Dropped event test:

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

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

7. Logs to Check

Nginx access log

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

Or:

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

A valid request should contain:

POST /mailer/callback

Interpretation:

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:

tail -f var/logs/mautic_prod.php

Depending on the Mautic log configuration:

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

Useful plugin messages include:

Processed SendGrid bounce for ...
Processed SendGrid dropped for ...
Processed SendGrid unsubscribe for ...

If the email cannot be matched:

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:

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:
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

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.

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?

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:

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

Then verify:

composer show azlobin/mautic-sendgrid-callback

It should show:

versions : * 1.5.11

For an already installed package, use:

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

After updating:

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:

Deliverability Data → Bounced

They appear in the webhook payload like this:

[
  {
    "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:

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:

HTTP 200
SendGrid Callback processed (1)

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.