[{"data":1,"prerenderedAt":20},["ShallowReactive",2],{"blog-handle-email-bounces":3},{"slug":4,"title":5,"description":6,"date":7,"author":8,"image":12,"tags":13,"content":18,"readingTime":19},"handle-email-bounces","How to handle email bounces programmatically","Build proper bounce handling with webhooks, suppression lists, and retry logic to protect your sender reputation.","2026-03-29",{"id":9,"name":10,"avatar":11},"paulo-castellano","Paulo Castellano","/images/authors/paulo.png","/images/blog/handle-email-bounces/cover.jpg",[14,15,16,17],"deliverability","webhooks","best-practices","tutorial","\u003Cp>Every email you send to a dead address is a signal to mailbox providers that you do not know what you are doing. Enough of those signals and your emails stop reaching real people. Bounce handling is not a nice-to-have. It is infrastructure that protects your ability to send email at all.\u003C/p>\n\u003Cp>This guide walks through building programmatic bounce handling with \u003Ca href=\"/email-api\">Sendkit&#39;s email API\u003C/a>: webhook endpoints, suppression lists, retry logic, and complaint processing. By the end you will have a system that keeps your bounce rate well under the thresholds that matter.\u003C/p>\n\u003Ch2>What are email bounces\u003C/h2>\n\u003Cp>A bounce happens when an email cannot be delivered to the recipient. The receiving mail server sends back a rejection, and your email provider captures it. There are two types.\u003C/p>\n\u003Cp>\u003Cstrong>Hard bounces\u003C/strong> are permanent failures. The email address does not exist, the domain is invalid, or the recipient server has explicitly blocked delivery. Common SMTP codes: 550 (mailbox not found), 551 (user not local), 552 (mailbox full, sometimes classified as hard). Once you get a hard bounce, that address is dead to you. Do not send to it again.\u003C/p>\n\u003Cp>\u003Cstrong>Soft bounces\u003C/strong> are temporary failures. The mailbox is full, the server is temporarily down, a rate limit was hit, or there is a DNS issue on the receiving end. SMTP codes in the 4xx range. These might resolve on their own, so retrying makes sense, but only up to a point.\u003C/p>\n\u003Cp>The distinction matters because your handling logic should be completely different for each type.\u003C/p>\n\u003Ch2>Why bounces damage sender reputation\u003C/h2>\n\u003Cp>Mailbox providers track your bounce rate as a core signal for sender reputation. Google and Yahoo enforced a hard ceiling in 2024: if your bounce rate exceeds 2%, you start getting throttled or blocked. Microsoft followed with similar enforcement across Outlook.com and Hotmail in 2025.\u003C/p>\n\u003Cp>The math is simple. If you send 10,000 emails and 300 bounce, you are at 3%. That is enough for Gmail to start routing your mail to spam for everyone on your list, including the valid addresses. Your deliverability craters, open rates drop, and you are stuck in a hole that takes weeks to dig out of.\u003C/p>\n\u003Cp>The fix is not complicated: stop sending to addresses that bounce, and \u003Ca href=\"/email-validations\">validate addresses before they enter your list\u003C/a>. But you need the plumbing to make that happen automatically.\u003C/p>\n\u003Cp>For a deeper look at all the factors that affect inbox placement, read our \u003Ca href=\"/blog/improve-email-deliverability\">guide to improving email deliverability\u003C/a>.\u003C/p>\n\u003Ch2>Setting up a webhook endpoint for bounce events\u003C/h2>\n\u003Cp>Sendkit sends webhook events in real time when something happens to an email you sent. The events relevant to bounce handling are:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Ccode>email.bounced\u003C/code> — the email bounced (hard or soft)\u003C/li>\n\u003Cli>\u003Ccode>email.complained\u003C/code> — the recipient marked your email as spam\u003C/li>\n\u003Cli>\u003Ccode>email.delivered\u003C/code> — successful delivery confirmation\u003C/li>\n\u003Cli>\u003Ccode>email.failed\u003C/code> — the email failed to send entirely\u003C/li>\n\u003C/ul>\n\u003Cp>You configure webhook endpoints in the \u003Ca href=\"https://docs.sendkit.dev/webhooks/introduction\">Sendkit dashboard\u003C/a> or via the API. Sendkit signs every webhook payload with HMAC-SHA256, sent in the \u003Ccode>x-sendkit-signature\u003C/code> header, so you can verify the request is legitimate.\u003C/p>\n\u003Cp>\u003Cimg src=\"/images/blog/handle-email-bounces/inline-1.jpg\" alt=\"Monitoring dashboard showing email delivery metrics\" width=\"1080\" height=\"720\" loading=\"lazy\" />\u003C/p>\n\u003Ch2>Building the webhook handler\u003C/h2>\n\u003Cp>Here is a complete Express.js webhook handler that verifies the signature and routes events to the right processing logic.\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">import\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> express \u003C/span>\u003Cspan style=\"color:#FF79C6\">from\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">express\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">import\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> crypto \u003C/span>\u003Cspan style=\"color:#FF79C6\">from\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">crypto\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">import\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { Sendkit } \u003C/span>\u003Cspan style=\"color:#FF79C6\">from\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">@sendkitdev/sdk\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> app \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#50FA7B\"> express\u003C/span>\u003Cspan style=\"color:#F8F8F2\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> sendkit \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Sendkit\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">sk_your_api_key\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> WEBHOOK_SECRET \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> process.env.\u003C/span>\u003Cspan style=\"color:#BD93F9\">SENDKIT_WEBHOOK_SECRET\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">app.\u003C/span>\u003Cspan style=\"color:#50FA7B\">post\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">/webhooks/sendkit\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, express.\u003C/span>\u003Cspan style=\"color:#50FA7B\">raw\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({ type\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">application/json\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> }), (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">req\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">res\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> signature \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> req.headers[\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">x-sendkit-signature\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">] \u003C/span>\u003Cspan style=\"color:#FF79C6\">as\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> expectedSignature \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> crypto\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    .\u003C/span>\u003Cspan style=\"color:#50FA7B\">createHmac\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">sha256\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, WEBHOOK_SECRET)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    .\u003C/span>\u003Cspan style=\"color:#50FA7B\">update\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(req.body)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    .\u003C/span>\u003Cspan style=\"color:#50FA7B\">digest\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">hex\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (signature \u003C/span>\u003Cspan style=\"color:#FF79C6\">!==\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> expectedSignature) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    return\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> res.\u003C/span>\u003Cspan style=\"color:#50FA7B\">status\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">401\u003C/span>\u003Cspan style=\"color:#F8F8F2\">).\u003C/span>\u003Cspan style=\"color:#50FA7B\">json\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({ error\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">Invalid signature\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> event \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#BD93F9\"> JSON\u003C/span>\u003Cspan style=\"color:#F8F8F2\">.\u003C/span>\u003Cspan style=\"color:#50FA7B\">parse\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(req.body.\u003C/span>\u003Cspan style=\"color:#50FA7B\">toString\u003C/span>\u003Cspan style=\"color:#F8F8F2\">());\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  switch\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (event.type) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    case\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">email.bounced\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#50FA7B\">      handleBounce\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(event.data);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">      break\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    case\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">email.complained\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#50FA7B\">      handleComplaint\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(event.data);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">      break\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    case\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">email.failed\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#50FA7B\">      handleFailure\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(event.data);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">      break\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  res.\u003C/span>\u003Cspan style=\"color:#50FA7B\">status\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">200\u003C/span>\u003Cspan style=\"color:#F8F8F2\">).\u003C/span>\u003Cspan style=\"color:#50FA7B\">json\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({ received\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#BD93F9\"> true\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">});\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>Always verify the signature before processing any webhook payload. Without verification, anyone can send fake bounce events to your endpoint and corrupt your suppression list.\u003C/p>\n\u003Ch2>Processing hard bounces\u003C/h2>\n\u003Cp>Hard bounces are non-negotiable. The address is dead. Suppress it immediately and never send to it again.\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#50FA7B\"> handleBounce\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =\u003C/span>\u003Cspan style=\"color:#FF79C6\"> async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  recipient\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  bounce_type\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  error_code\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  message\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">}) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (data.bounce_type \u003C/span>\u003Cspan style=\"color:#FF79C6\">===\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">hard\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.suppressionList.\u003C/span>\u003Cspan style=\"color:#50FA7B\">upsert\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      create\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">hard_bounce\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        errorCode\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.error_code,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        suppressedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      update\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">hard_bounce\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        errorCode\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.error_code,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        suppressedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.contact.\u003C/span>\u003Cspan style=\"color:#50FA7B\">update\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { status\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">suppressed\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    console.\u003C/span>\u003Cspan style=\"color:#50FA7B\">log\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`Hard bounce: suppressed \u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">data.recipient\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    return\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (data.bounce_type \u003C/span>\u003Cspan style=\"color:#FF79C6\">===\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">soft\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#50FA7B\"> handleSoftBounce\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(data);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>No retry. No second chances. A hard bounce means the mailbox does not exist. Retrying just increases your bounce rate and damages your reputation further.\u003C/p>\n\u003Ch2>Processing soft bounces with exponential backoff\u003C/h2>\n\u003Cp>Soft bounces deserve retries, but with limits. Retry a few times with increasing delays. If the address keeps failing, treat it as a hard bounce.\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> MAX_SOFT_BOUNCES \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 3\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> BACKOFF_DELAYS \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> [\u003C/span>\u003Cspan style=\"color:#BD93F9\">60_000\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#BD93F9\">300_000\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#BD93F9\">3_600_000\u003C/span>\u003Cspan style=\"color:#F8F8F2\">]; \u003C/span>\u003Cspan style=\"color:#6272A4\">// 1min, 5min, 1hr\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#50FA7B\"> handleSoftBounce\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =\u003C/span>\u003Cspan style=\"color:#FF79C6\"> async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { recipient\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">; error_code\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> }) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> record \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#FF79C6\"> await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.softBounce.\u003C/span>\u003Cspan style=\"color:#50FA7B\">findUnique\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> bounceCount \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (record?.count \u003C/span>\u003Cspan style=\"color:#FF79C6\">??\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 0\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">+\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 1\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (bounceCount \u003C/span>\u003Cspan style=\"color:#FF79C6\">>=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> MAX_SOFT_BOUNCES) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.suppressionList.\u003C/span>\u003Cspan style=\"color:#50FA7B\">create\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">repeated_soft_bounce\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        errorCode\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.error_code,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">        suppressedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    console.\u003C/span>\u003Cspan style=\"color:#50FA7B\">log\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`Soft bounce limit reached: suppressed \u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">data.recipient\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    return\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.softBounce.\u003C/span>\u003Cspan style=\"color:#50FA7B\">upsert\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    create\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient, count\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 1\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, lastBouncedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">() },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    update\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { count\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> bounceCount, lastBouncedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">() },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> delay \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> BACKOFF_DELAYS[bounceCount \u003C/span>\u003Cspan style=\"color:#FF79C6\">-\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 1\u003C/span>\u003Cspan style=\"color:#F8F8F2\">];\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#50FA7B\">  setTimeout\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#FF79C6\">async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> () \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> sendkit.emails.\u003C/span>\u003Cspan style=\"color:#50FA7B\">send\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      from\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">notifications@yourdomain.com\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      to\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      subject\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">Your pending notification\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      html\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">&#x3C;p>Retry attempt&#x3C;/p>\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }, delay);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>Three strikes is a reasonable threshold. After three soft bounces, the address is either permanently broken or consistently unreliable. Either way, you do not want it on your list.\u003C/p>\n\u003Cp>\u003Cimg src=\"/images/blog/handle-email-bounces/inline-2.jpg\" alt=\"Server infrastructure handling webhook events\" width=\"1080\" height=\"720\" loading=\"lazy\" />\u003C/p>\n\u003Ch2>Building a suppression list\u003C/h2>\n\u003Cp>Your suppression list is a database table that you check before every single send. No exceptions. The schema is straightforward:\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">CREATE\u003C/span>\u003Cspan style=\"color:#FF79C6\"> TABLE\u003C/span>\u003Cspan style=\"color:#50FA7B\"> suppression_list\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  id \u003C/span>\u003Cspan style=\"color:#FF79C6\">SERIAL\u003C/span>\u003Cspan style=\"color:#FF79C6\"> PRIMARY KEY\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  email \u003C/span>\u003Cspan style=\"color:#FF79C6\">VARCHAR\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">255\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">UNIQUE\u003C/span>\u003Cspan style=\"color:#FF79C6\"> NOT NULL\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  reason \u003C/span>\u003Cspan style=\"color:#FF79C6\">VARCHAR\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">50\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">NOT NULL\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#6272A4\">-- hard_bounce, repeated_soft_bounce, complaint\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  error_code \u003C/span>\u003Cspan style=\"color:#FF79C6\">VARCHAR\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">20\u003C/span>\u003Cspan style=\"color:#F8F8F2\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  suppressed_at \u003C/span>\u003Cspan style=\"color:#FF79C6\">TIMESTAMP\u003C/span>\u003Cspan style=\"color:#FF79C6\"> DEFAULT\u003C/span>\u003Cspan style=\"color:#FF79C6\"> NOW\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  created_at \u003C/span>\u003Cspan style=\"color:#FF79C6\">TIMESTAMP\u003C/span>\u003Cspan style=\"color:#FF79C6\"> DEFAULT\u003C/span>\u003Cspan style=\"color:#FF79C6\"> NOW\u003C/span>\u003Cspan style=\"color:#F8F8F2\">()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">CREATE\u003C/span>\u003Cspan style=\"color:#FF79C6\"> INDEX\u003C/span>\u003Cspan style=\"color:#50FA7B\"> idx_suppression_email\u003C/span>\u003Cspan style=\"color:#FF79C6\"> ON\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> suppression_list (email);\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>Before every send, check the list:\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#50FA7B\"> sendEmail\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =\u003C/span>\u003Cspan style=\"color:#FF79C6\"> async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">to\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">subject\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">html\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> suppressed \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#FF79C6\"> await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.suppressionList.\u003C/span>\u003Cspan style=\"color:#50FA7B\">findUnique\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> to },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (suppressed) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    console.\u003C/span>\u003Cspan style=\"color:#50FA7B\">log\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`Skipping suppressed address: \u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">to\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\"> (\u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">suppressed.reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\">)`\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    return\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { skipped\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#BD93F9\"> true\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> suppressed.reason };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  return\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> sendkit.emails.\u003C/span>\u003Cspan style=\"color:#50FA7B\">send\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    from\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">notifications@yourdomain.com\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    to,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    subject,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    html,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>For bulk sends, query the suppression list in batch rather than one at a time. Pull all suppressed addresses for your recipient list in a single query and filter them out before you start sending.\u003C/p>\n\u003Cp>If you are managing \u003Ca href=\"/contacts\">contacts\u003C/a> through Sendkit, you can sync suppression status to keep everything consistent across your sending infrastructure.\u003C/p>\n\u003Ch2>Sendkit&#39;s automatic suppression\u003C/h2>\n\u003Cp>Here is the good news: Sendkit maintains its own suppression list automatically. When a hard bounce comes back, Sendkit suppresses that address across your account. You do not need to build any of the above to get basic protection.\u003C/p>\n\u003Cp>So why build your own? Three reasons.\u003C/p>\n\u003Cp>\u003Cstrong>Custom logic.\u003C/strong> You might want different thresholds for different email types. Transactional emails (password resets, order confirmations) might warrant more aggressive retries than marketing emails. Your own suppression layer lets you make those decisions.\u003C/p>\n\u003Cp>\u003Cstrong>Cross-system sync.\u003C/strong> If you send from multiple services or have addresses in a CRM, your suppression data needs to propagate everywhere. Your own database is the source of truth.\u003C/p>\n\u003Cp>\u003Cstrong>Audit trail.\u003C/strong> When someone asks why they stopped receiving emails, you can show exactly when the suppression happened, what caused it, and which bounce event triggered it.\u003C/p>\n\u003Cp>Think of Sendkit&#39;s automatic suppression as the safety net and your custom logic as the fine-tuned layer on top.\u003C/p>\n\u003Ch2>Monitoring bounce rates\u003C/h2>\n\u003Cp>You need visibility into your bounce rate on every send. Set up a simple monitoring function:\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#50FA7B\"> trackBounceRate\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =\u003C/span>\u003Cspan style=\"color:#FF79C6\"> async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">campaignId\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> stats \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#FF79C6\"> await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.emailEvent.\u003C/span>\u003Cspan style=\"color:#50FA7B\">groupBy\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    by\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> [\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F1FA8C\">event_type\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { campaignId },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    _count\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#BD93F9\"> true\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> total \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> stats.\u003C/span>\u003Cspan style=\"color:#50FA7B\">reduce\u003C/span>\u003Cspan style=\"color:#F8F8F2\">((\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">sum\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, \u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">s\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> sum \u003C/span>\u003Cspan style=\"color:#FF79C6\">+\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> s._count, \u003C/span>\u003Cspan style=\"color:#BD93F9\">0\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> bounces \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> stats.\u003C/span>\u003Cspan style=\"color:#50FA7B\">find\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">s\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> s.event_type \u003C/span>\u003Cspan style=\"color:#FF79C6\">===\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">email.bounced\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">)?._count \u003C/span>\u003Cspan style=\"color:#FF79C6\">??\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 0\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  const\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> bounceRate \u003C/span>\u003Cspan style=\"color:#FF79C6\">=\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (bounces \u003C/span>\u003Cspan style=\"color:#FF79C6\">/\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> total) \u003C/span>\u003Cspan style=\"color:#FF79C6\">*\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 100\u003C/span>\u003Cspan style=\"color:#F8F8F2\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  if\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (bounceRate \u003C/span>\u003Cspan style=\"color:#FF79C6\">>\u003C/span>\u003Cspan style=\"color:#BD93F9\"> 1.5\u003C/span>\u003Cspan style=\"color:#F8F8F2\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6272A4\">    // Alert before hitting the 2% threshold\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">    await\u003C/span>\u003Cspan style=\"color:#50FA7B\"> alertOps\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#F1FA8C\">`Campaign \u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">campaignId\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\"> bounce rate at \u003C/span>\u003Cspan style=\"color:#FF79C6\">${\u003C/span>\u003Cspan style=\"color:#F8F8F2\">bounceRate.\u003C/span>\u003Cspan style=\"color:#50FA7B\">toFixed\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(\u003C/span>\u003Cspan style=\"color:#BD93F9\">2\u003C/span>\u003Cspan style=\"color:#F8F8F2\">)\u003C/span>\u003Cspan style=\"color:#FF79C6\">}\u003C/span>\u003Cspan style=\"color:#F1FA8C\">%`\u003C/span>\u003Cspan style=\"color:#F8F8F2\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  return\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { total, bounces, bounceRate };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>Set your alert threshold at 1.5%, not 2%. You want to catch problems before they trigger enforcement from Google, Yahoo, or Microsoft. By the time you are at 2%, damage is already being done.\u003C/p>\n\u003Cp>The best way to keep bounce rates low is to prevent bad addresses from entering your list in the first place. Run \u003Ca href=\"/email-validations\">email validation\u003C/a> at the point of collection and periodically against your existing list. We explain how in our guide to \u003Ca href=\"/blog/validate-email-addresses-before-sending\">validating email addresses before sending\u003C/a>.\u003C/p>\n\u003Ch2>Handling complaints\u003C/h2>\n\u003Cp>Complaint events (\u003Ccode>email.complained\u003C/code>) fire when a recipient clicks &quot;Report Spam&quot; in their email client. This is worse than a bounce. A bounce means the address is bad. A complaint means a real person actively does not want your email.\u003C/p>\n\u003Cpre class=\"shiki dracula\" style=\"background-color:#282A36;color:#F8F8F2\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">const\u003C/span>\u003Cspan style=\"color:#50FA7B\"> handleComplaint\u003C/span>\u003Cspan style=\"color:#FF79C6\"> =\u003C/span>\u003Cspan style=\"color:#FF79C6\"> async\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> (\u003C/span>\u003Cspan style=\"color:#FFB86C;font-style:italic\">data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { recipient\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#8BE9FD;font-style:italic\"> string\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> }) \u003C/span>\u003Cspan style=\"color:#FF79C6\">=>\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.suppressionList.\u003C/span>\u003Cspan style=\"color:#50FA7B\">upsert\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    create\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">complaint\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      suppressedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    update\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      reason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">complaint\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">      suppressedAt\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#FF79C6;font-weight:bold\"> new\u003C/span>\u003Cspan style=\"color:#50FA7B\"> Date\u003C/span>\u003Cspan style=\"color:#F8F8F2\">(),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF79C6\">  await\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> db.contact.\u003C/span>\u003Cspan style=\"color:#50FA7B\">update\u003C/span>\u003Cspan style=\"color:#F8F8F2\">({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    where\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { email\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> data.recipient },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">    data\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> { status\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">unsubscribed\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\">, unsubscribeReason\u003C/span>\u003Cspan style=\"color:#FF79C6\">:\u003C/span>\u003Cspan style=\"color:#E9F284\"> '\u003C/span>\u003Cspan style=\"color:#F1FA8C\">spam_complaint\u003C/span>\u003Cspan style=\"color:#E9F284\">'\u003C/span>\u003Cspan style=\"color:#F8F8F2\"> },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F8F8F2\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\u003Cp>Suppress immediately. Do not send a &quot;sorry to see you go&quot; email. Do not add them to a re-engagement flow. They reported you as spam. The only correct response is to stop emailing them.\u003C/p>\n\u003Cp>Google&#39;s postmaster tools show complaint rates separately from bounce rates, and they weight complaints more heavily. A complaint rate above 0.1% is a red flag. Above 0.3% and you are in serious trouble.\u003C/p>\n\u003Ch2>Putting it all together\u003C/h2>\n\u003Cp>Proper bounce handling is a pipeline: authenticate your emails with \u003Ca href=\"/blog/setup-dmarc-dkim-spf\">SPF, DKIM, and DMARC\u003C/a>, validate addresses before they enter your list, process bounces and complaints in real time via webhooks, maintain a suppression list, and monitor your rates.\u003C/p>\n\u003Cp>Sendkit handles the heavy lifting — automatic suppression, webhook delivery, authentication setup. Your job is to wire up the webhook endpoint, add your custom business logic, and keep an eye on the metrics.\u003C/p>\n\u003Cp>The full webhook documentation is at \u003Ca href=\"https://docs.sendkit.dev/webhooks/introduction\">docs.sendkit.dev/webhooks/introduction\u003C/a>. Set up your endpoint, deploy the handler code from this guide, and stop sending to dead addresses.\u003C/p>\n",9,1775845590698]