---
title: Handle hard bounce emails
description: Handle hard bounce notifications to automatically remove invalid email addresses from your mailing lists and maintain good sender reputation.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-routing/hard-bounce-handling.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Handle hard bounce emails

Detect and handle hard bounce emails to maintain sender reputation and manage undeliverable addresses

Handle hard bounce notifications to automatically remove invalid email addresses from your mailing lists and maintain good sender reputation.

## What are hard bounces?

Hard bounces occur when an email cannot be delivered due to permanent reasons:

* Invalid email address: The email address does not exist
* Domain does not exist: The domain name is invalid or expired
* Mailbox full: The recipient's mailbox has exceeded storage limits
* Email blocked: The recipient's server permanently rejects emails

## Configuration

Configure your worker to handle bounce notifications:

* [  wrangler.jsonc ](#tab-panel-6820)
* [  wrangler.toml ](#tab-panel-6821)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "bounce-handler",

  // Set this to today's date

  "compatibility_date": "2026-04-16",

  "send_email": [

    {

      "name": "EMAIL"

    }

  ],

  "kv_namespaces": [

    {

      "binding": "SUPPRESSION_LIST",

      "id": "your-kv-namespace-id"

    }

  ]

}


```

Explain Code

TOML

```

name = "bounce-handler"

# Set this to today's date

compatibility_date = "2026-04-16"


[[send_email]]

name = "EMAIL"


[[kv_namespaces]]

binding = "SUPPRESSION_LIST"

id = "your-kv-namespace-id"


```

Explain Code

## Hard bounce detection

JavaScript

```

import * as PostalMime from 'postal-mime';


export default {

  async email(message, env, ctx) {

    // Parse the raw email message

    const parser = new PostalMime.default();

    const rawEmail = new Response(message.raw);

    const email = await parser.parse(await rawEmail.arrayBuffer());


    // Check if this is a bounce notification

    if (isBounceNotification(email)) {

      const bounceInfo = await parseBounceInfo(email);


      if (bounceInfo.type === 'hard') {

        await handleHardBounce(bounceInfo, env);

        console.log(`Hard bounce processed for: ${bounceInfo.originalRecipient}`);

        return;

      }

    }


    // Forward non-bounce emails normally

    await message.forward('admin@yourdomain.com');

  },

};


function isBounceNotification(email) {

  // Check common bounce indicators

  const subject = email.subject?.toLowerCase() || '';

  const fromAddress = email.from?.address?.toLowerCase() || '';


  // Common bounce indicators

  const bounceSubjects = [

    'mail delivery failed',

    'undelivered mail returned to sender',

    'delivery status notification',

    'returned mail',

    'mail system error'

  ];


  const bounceFromPatterns = [

    'mailer-daemon',

    'mail-daemon',

    'postmaster',

    'noreply',

    'bounce'

  ];


  return bounceSubjects.some(phrase => subject.includes(phrase)) ||

         bounceFromPatterns.some(pattern => fromAddress.includes(pattern));

}


async function parseBounceInfo(email) {

  const text = email.text || '';

  const html = email.html || '';

  const content = text + ' ' + html;


  // Extract original recipient email

  const recipientMatch = content.match(/(?:to|for|recipient):\s*([^\s<]+@[^\s>]+)/i) ||

                        content.match(/([^\s<]+@[^\s>]+)/);


  const originalRecipient = recipientMatch ? recipientMatch[1] : null;


  // Determine bounce type based on content

  const hardBounceIndicators = [

    'user unknown',

    'no such user',

    'invalid recipient',

    'recipient address rejected',

    'mailbox unavailable',

    'domain not found',

    '5.1.1', // SMTP error code for bad destination mailbox

    '5.1.2', // SMTP error code for bad destination system

    '5.4.1', // SMTP error code for no answer from host

  ];


  const isHardBounce = hardBounceIndicators.some(indicator =>

    content.toLowerCase().includes(indicator.toLowerCase())

  );


  return {

    type: isHardBounce ? 'hard' : 'soft',

    originalRecipient,

    reason: extractBounceReason(content),

    timestamp: new Date().toISOString()

  };

}


function extractBounceReason(content) {

  // Extract the specific error message

  const reasonPatterns = [

    /diagnostic[- ]code:\s*(.+)/i,

    /reason:\s*(.+)/i,

    /error:\s*(.+)/i,

    /(5\.\d+\.\d+[^.\n]*)/i

  ];


  for (const pattern of reasonPatterns) {

    const match = content.match(pattern);

    if (match) {

      return match[1].trim().split('\n')[0]; // Take first line only

    }

  }


  return 'Unknown bounce reason';

}


async function handleHardBounce(bounceInfo, env) {

  if (!bounceInfo.originalRecipient) {

    console.log('Could not extract original recipient from bounce');

    return;

  }


  // Add to suppression list in KV

  await env.SUPPRESSION_LIST.put(

    bounceInfo.originalRecipient,

    JSON.stringify({

      type: 'hard_bounce',

      reason: bounceInfo.reason,

      timestamp: bounceInfo.timestamp,

      status: 'suppressed'

    }),

    {

      metadata: {

        bounceType: 'hard',

        addedDate: bounceInfo.timestamp

      }

    }

  );


  console.log(`Added ${bounceInfo.originalRecipient} to suppression list: ${bounceInfo.reason}`);

}


```

Explain Code

## Testing hard bounce handling

Create a test bounce notification:

Terminal window

```

curl --request POST 'http://localhost:8787/cdn-cgi/handler/email' \

  --url-query 'from=mailer-daemon@example.com' \

  --url-query 'to=bounce-handler@yourdomain.com' \

  --header 'Content-Type: application/json' \

  --data-raw 'From: Mail Delivery Subsystem <mailer-daemon@example.com>

To: bounce-handler@yourdomain.com

Subject: Mail delivery failed: returning message to sender

Date: Wed, 28 Aug 2024 10:30:00 +0000

Message-ID: <bounce123@example.com>


This message was created automatically by mail delivery software.


A message that you sent could not be delivered to one or more of its

recipients. This is a permanent error. The following address(es) failed:


  nonexistent@example.com

    SMTP error from remote mail server after RCPT TO:<nonexistent@example.com>:

    host mx.example.com [192.168.1.1]: 550 5.1.1 User unknown


------ This is a copy of the message, including all the headers. ------


Return-path: <sender@yourdomain.com>

From: sender@yourdomain.com

To: nonexistent@example.com

Subject: Welcome to our service

Message-ID: <original123@yourdomain.com>


Welcome! Thanks for signing up.'


```

Explain Code

## Checking suppression list

Add a utility function to check if an email is suppressed before sending:

JavaScript

```

async function isEmailSuppressed(email, env) {

  const suppressionEntry = await env.SUPPRESSION_LIST.get(email);


  if (suppressionEntry) {

    const data = JSON.parse(suppressionEntry);

    console.log(`Email ${email} is suppressed: ${data.reason}`);

    return true;

  }


  return false;

}


// Use before sending emails

export async function sendEmail(recipient, subject, content, env) {

  if (await isEmailSuppressed(recipient, env)) {

    console.log(`Skipping email to suppressed address: ${recipient}`);

    return { success: false, reason: "suppressed" };

  }


  // Proceed with email sending

  // ... your email sending logic

}


```

Explain Code

## Best practices

1. **Monitor bounce rates**: Track bounce rates to maintain good sender reputation
2. **Automatic cleanup**: Regularly review and clean suppression lists
3. **Double opt-in**: Use double opt-in to reduce invalid addresses
4. **Retry logic**: Implement appropriate retry logic for soft bounces
5. **Logging**: Log all bounce handling for debugging and analytics

## Next steps

* Learn about [email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/) to improve deliverability
* Set up [metrics and analytics](https://developers.cloudflare.com/email-service/observability/metrics-analytics/) to monitor bounce rates
* Implement [spam filtering](https://developers.cloudflare.com/email-service/examples/email-routing/spam-filtering/) for incoming emails

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/email-service/","name":"Email Service"}},{"@type":"ListItem","position":3,"item":{"@id":"/email-service/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/examples/email-routing/","name":"Email routing"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/examples/email-routing/hard-bounce-handling/","name":"Handle hard bounce emails"}}]}
```
