---
title: "Building a Resend analytics dashboard"
excerpt: "Sending emails is painful. Knowing if they actually work? Worse. Here's how to track every bounce, open, and delivery in real time."
authors: "Alasdair Brown"
categories: "I Built This!"
createdOn: "2025-01-20 00:00:00"
publishedOn: "2025-01-20 00:00:00"
updatedOn: "2025-04-24 00:00:00"
status: "published"
---

<p><a href="https://resend.com"><u>Resend</u></a> is a developer platform for sending transactional and marketing emails. If you don’t do much with email, well done, you’ve won in life. But if you do, Resend is probably going to save you a bunch of headaches.</p><p>Once you’re set up to send with Resend, how do you know you’re “doing email right”? Resend captures events about the status of your emails - when it's sent, delivered, bounced, etc. - and offers webhooks so you can push these events elsewhere. Sending these webhooks to Tinybird’s Events API works out of the box, so with a little bit of SQL you can finally know if “Dear Sir/Madam” is a better email intro than “Oi, you”.</p><p>The <a href="https://git.new/dsat"><u>dev stack analytics template</u></a> contains analytics dashboards for the most popular dev tools, and this week I’ll be working on the Resend dashboard, with a new video each day working through one chart at a time.</p><h2 id="what-events-have-we-got">What events have we got?</h2><p><a href="https://resend.com/docs/dashboard/webhooks/event-types"><u>Resend has a bunch of different events</u></a> you can work with. There’s 3 objects you can get events for: emails, domains and contacts. I’ll start by focusing on emails. That gives you 7 different event types to look at:</p><ol><li>email.sent</li><li>email.delivered</li><li>email.delivery_delayed</li><li>email.complained</li><li>email.bounced</li><li>email.opened</li><li>email.clicked</li></ol><p>The <code>opened</code> and <code>clicked</code> events depend on embedding a tracking pixel, which is not something I recommend if you want to stay out of Spam, so I’ll pretend they don’t exist.</p><p>Just <code>sent</code>, <code>delivered</code> and <code>bounced</code> should cover a vast majority of real world events and give you plenty to work with.</p><h2 id="getting-events">Getting events</h2><p>Configuring Resend webhooks to send to Tinybird only takes a minute or two and is <a href="https://www.tinybird.co/docs/get-data-in/guides/ingest-from-resend"><u>covered in the docs</u></a>. Once configured, you’ll have a Data Source receiving events that looks like this:</p><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfw9iL584AsYHLym4ya7VWPkXQipctQfaLaPcchNd-CYNND3L8dsNSmWRUA1rhks5cmuxCYMIeWEQuDAQmnxqwMenP6f18ikxTpL5WahnN6-gxeTVKuJEpxC3TUdFt0zYvvqd4e?key=-jZYxRm6PajsC3RYWhhs0Czp" class="kg-image" alt="" loading="lazy" width="2048" height="1163"></figure><p>If you’re not yet sending production emails, or want to force specific events like bounces, you can <a href="https://resend.com/docs/dashboard/emails/send-test-emails"><u>send test emails</u></a>.</p><h2 id="querying-events">Querying events</h2><p>The Data Source of events is a time ordered append log. The <code>event_type</code> column will let you quickly filter for the specific events. With a Pipe, you can start to query your events in SQL.</p><p>A query like this will give you the <code>email.sent</code> events in time order:</p>
<!--kg-card-begin: html-->
<iframe width="100%" src="https://snippets.tinybird.co/XQAAAAKZAAAAAAAAAABBKUqGk9nLKu-TqbsBIaSOs3lD9ghf3pRMZLbLLlo2NKXvPbd6aGY8jOxgXMKQw46FGEnX6RHvqaDkYMZh4j8Y8TqlBiLqXdWX3vGntBptD_HI36as4rXK5oVPr4nnrzy-ztMDVa7TiNkVaFc2WNamPHPBIEj3Bl0RDHzwsRGU3HpIB2ykGNQpCHgRX__7erAA/embed"></iframe>
<!--kg-card-end: html-->
<p>Let’s say you want to plot your email sends over time. You don’t need the whole event, just a count that is aggregated by some time granularity that you can plot over time. You can use a query like this:</p>
<!--kg-card-begin: html-->
<iframe width="100%" src="https://snippets.tinybird.co/XQAAAALgAAAAAAAAAABBKUqGk9nLKv9v4bsBIaSOs3lD9fROa70bkBao8DdpcB3kCHB5M7YpQ7q3QER8oBCgcpWBagTTWNPBGdywJGEyUlYidn9GKtfrQRBzHcv-67-QZB-EEwhQuir0_uvi6ZvqdLvCLSYDfyQ3zatsRwrp7mDehyYvmrHUAdeoFnMY2-kpW1X8fmkejUI49_f6BEKM_DT2LwhontNGd5tZjtlFaCuc5gf9PEq9tXy_gS9AQ2WKQ9xXfe6AB8S8CG4ay6UA1zj8_uizQA/embed"></iframe>
<!--kg-card-end: html-->
<p>This will count how many emails are sent per day, giving you a result like:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="333"><col width="333"></colgroup><tbody><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">period</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">sent</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2025-01-18</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3456</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2025-01-19</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">4678</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2025-01-20</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Inter,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">5678</span></p></td></tr></tbody></table>
<!--kg-card-end: html-->
<p>That’s a super simple example, but from there you can start to build out anything you want to see.</p><h2 id="building-the-dashboard">Building the dashboard</h2><p>Over the next week, I’ll be working towards a dashboard that looks like the wireframe below, showing how each query and chart component is built. The code for the whole thing will be available in the <a href="https://git.new/dsat"><u>dev stack analytics template</u></a>.</p><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeKK9YW65L0DAQhAUcRbCdSClezentWxJTgGVKHbAI0twsa3uMo-FrhyN8UVf1XSDtQrKq6nDsH2HgYrwGIEN-fcxjtA2X9ofry5oyaXmXdefYN4GJeMjniEIle4PtNSL06Ctek2w?key=-jZYxRm6PajsC3RYWhhs0Czp" class="kg-image" alt="" loading="lazy" width="2048" height="1216"></figure>
