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
| Type | Cause | Examples | Action Required |
|---|---|---|---|
| Hard bounce | Permanent delivery failure | Address does not exist, domain does not exist, recipient blocked sender | Remove immediately, never retry |
| Soft bounce | Temporary delivery failure | Mailbox full, server temporarily unavailable, message too large | Retry 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
bouncedand 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 Rate | Status |
|---|---|
| < 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.