How to monitor distinct email opens using Mailgun webhooks

I’ve been working with the Mailgun API recently and got the basic functionality running smoothly. However, I’m running into an issue with tracking email opens through their webhook system.

The problem is that Mailgun fires an open event every time someone views the same email message. If a recipient opens the same email multiple times, I get multiple webhook notifications for each open.

What I need is a way to identify when an email gets opened for the first time only. I want to track unique opens per message rather than total opens.

Should I be looking at specific fields in the webhook payload to identify individual emails? Or would it be better to include custom metadata when sending the original message to help with tracking?

I’m using PHP for the webhook handling, but I think this is more about understanding which Mailgun data points are most reliable for this use case.

I use Mailgun’s custom variables with a unique tracking ID in each email’s custom data field. Way cleaner for webhook identification since the tracking ID comes back in every webhook event. Just maintain a simple lookup table of processed opens - no need to combine multiple fields. This approach saves you from edge cases where message IDs might act weird. Check your tracking ID against your opens table and ignore duplicate events for the same ID. Minimal overhead and your PHP handler logic stays simple.

Had this exact problem six months ago with our newsletter tracking. Use the message-id from the webhook payload plus the recipient email - that combo gives you a unique identifier for each email-recipient pair. I set up a simple database table: message_id, recipient_email, first_opened_at, and total_opens. When a webhook hits, I check if that message-id and recipient combo exists. If it’s new, I log it as a unique open. If it exists, I just bump the total_opens counter without firing the unique open logic. Mailgun’s message-id stays consistent across all events for the same sent message, so it’s rock solid. Way cleaner than trying to mess with custom metadata since the data’s already guaranteed to be in every webhook payload.

there’s actually a simpler way - create a hash from message-id + recipient email and store it in redis or your cache. when the webhook fires, check if the hash exists. if it doesn’t, it’s a unique open so process it and store the hash. you don’t need a full database table, just a lightweight key store. i’ve been using this approach for 2 years and it works great.