Keplars

Bounce Handling

Understand hard and soft bounces, safe thresholds, and how to query bounced contacts

Bounce Handling

A bounce occurs when a receiving mail server rejects your email. Bounces are the fastest way to damage your sender reputation if left unmanaged.

Hard Bounce vs Soft Bounce

TypeCauseExamplesAction Required
Hard bouncePermanent delivery failureAddress does not exist, domain does not exist, recipient blocked senderRemove immediately, never retry
Soft bounceTemporary delivery failureMailbox full, server temporarily unavailable, message too largeRetry automatically (Keplars handles this)

What Keplars Does Automatically

  • Records every bounce against the contact record
  • Increments a bounce counter per contact
  • After a hard bounce, marks the contact as bounced and suppresses future sends
  • Retries soft bounces up to 3 times with exponential backoff before converting to a hard bounce record

Bounce Webhook

Subscribe to email.bounced to receive real-time notifications:

{
  "event": "email.bounced",
  "email_id": "msg_01jx...",
  "to": "[email protected]",
  "bounce_type": "hard",
  "bounce_code": "550",
  "bounce_message": "5.1.1 The email account that you tried to reach does not exist",
  "timestamp": "2026-06-18T10:30:00Z"
}

See Webhooks to configure your endpoint.

Safe Bounce Rate Threshold

Hard Bounce RateStatus
< 2%Safe
2%–5%Warning: clean your list immediately
> 5%Danger: sending may be paused

Google and Yahoo enforce a 0.10% spam complaint rate threshold. Hard bounce rate compounds this risk.

Query Bounced Contacts

curl "https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id" \
  -H "Authorization: Bearer $KEPLARS_API_KEY"
const res = await fetch(
  'https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id',
  { headers: { Authorization: `Bearer ${process.env.KEPLARS_API_KEY}` } },
);
const { data } = await res.json();
import requests, os
res = requests.get(
    'https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id',
    headers={'Authorization': f'Bearer {os.environ["KEPLARS_API_KEY"]}'},
)
data = res.json()['data']
package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    req, _ := http.NewRequest("GET",
        "https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id", nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("KEPLARS_API_KEY"))
    resp, _ := (&http.Client{}).Do(req)
    defer resp.Body.Close()
    b, _ := io.ReadAll(resp.Body)
    fmt.Println(string(b))
}
<?php
$ch = curl_init('https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('KEPLARS_API_KEY'),
    ],
]);
$data = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);
using System.Net.Http;

var client = new HttpClient();
client.DefaultRequestHeaders.Add(
    "Authorization", $"Bearer {Environment.GetEnvironmentVariable("KEPLARS_API_KEY")}"
);
var response = await client.GetAsync(
    "https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id"
);
var body = await response.Content.ReadAsStringAsync();
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let res = client
        .get("https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id")
        .header("Authorization", format!("Bearer {}", std::env::var("KEPLARS_API_KEY")?))
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{}", data);
    Ok(())
}
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = std::string("Authorization: Bearer ") + std::getenv("KEPLARS_API_KEY");
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
}
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    CURL* curl = curl_easy_init();
    char auth[256];
    snprintf(auth, sizeof(auth), "Authorization: Bearer %s", getenv("KEPLARS_API_KEY"));
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, auth);
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/contacts/list?status=bounced&workspace_id=your-workspace-id");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}

Use the results to remove these addresses from your upstream CRM or database so they do not re-enter your sending lists.

Never send to a hard-bounced address. Continuing to send to addresses that have hard bounced signals to ISPs that you are not managing your list, which directly lowers your sender reputation score.

On this page