Delivery & Webhooks
Understand the email send lifecycle, SES webhook processing, bounce handling, and delivery monitoring.
When Kraiter sends an email, it goes through a lifecycle of states from queued to delivered (or bounced). Kraiter integrates with AWS SES webhooks to track every stage of delivery and automatically handle bounces, complaints, and other delivery events.
Send lifecycle
Every email sent through Kraiter follows this lifecycle:
queued → sent → delivered
↘ bounced
↘ complainedStates
| State | Description |
|---|---|
queued | The email has been accepted by Kraiter and is waiting to be sent to SES |
sent | The email has been submitted to SES for delivery |
delivered | SES confirmed the recipient's mail server accepted the email |
bounced | The email bounced — the recipient's mail server rejected it |
complained | The recipient reported the email as spam |
A send starts in the queued state when you call the send API or when a sequence step fires. It moves to sent once Kraiter submits it to SES, and then transitions to a final state based on SES feedback.
Checking send status
const send = await kraiter.sends.get('snd_abc123def456');
console.log(send.status); // 'queued', 'sent', 'delivered', 'bounced', 'complained'
console.log(send.sentAt); // When submitted to SES
console.log(send.deliveredAt); // When delivered (if delivered)curl https://api.kraiter.com/sends/snd_abc123def456 \
-H "Authorization: Bearer YOUR_API_KEY"SES webhook processing
Kraiter receives delivery notifications from AWS SES via SNS webhooks. When SES processes an email, it sends notifications for delivery, bounce, complaint, open, and click events. Kraiter processes these automatically.
What happens on each event
Delivery notification:
- Send status updated to
delivered email.deliveredsystem event created for the contact- Delivery timestamp recorded
Bounce notification:
- Send status updated to
bounced email.bouncedsystem event created- Bounce type and reason recorded
- Contact may be suppressed (see bounce types below)
Complaint notification:
- Send status updated to
complained email.complainedsystem event created- Contact is automatically suppressed with reason
complaint
Open notification:
email.openedsystem event created- Contact's derived properties updated (
lastEmailOpenedAt,emailsOpenedCount)
Click notification:
email.clickedsystem event created- The clicked URL and timestamp are recorded
- Contact's derived properties updated (
lastEmailClickedAt,emailsClickedCount)
Bounce types
SES classifies bounces into two categories:
Hard bounces
A hard bounce means the email address is permanently undeliverable. Common reasons:
- The email address does not exist
- The domain does not exist
- The mailbox has been disabled
Kraiter's response: The contact is automatically suppressed with reason bounce. No further emails will be sent to this address.
Soft bounces
A soft bounce means the delivery failed temporarily. Common reasons:
- The recipient's mailbox is full
- The receiving mail server is temporarily unavailable
- The message is too large
Kraiter's response: Soft bounces are recorded but do not trigger suppression. SES automatically retries soft-bounced messages for up to 12 hours. If the retries fail, the bounce is upgraded to a hard bounce.
Bounce sub-types
Each bounce includes a sub-type with more detail:
| Sub-type | Category | Description |
|---|---|---|
Undetermined | Hard | Could not determine the specific reason |
DoesNotExist | Hard | Email address does not exist |
MessageTooLarge | Soft | Message exceeds size limit |
MailboxFull | Soft | Recipient's mailbox is full |
ContentRejected | Soft | Content was rejected by the mail server |
General | Soft | General temporary failure |
Complaint handling
When a recipient marks your email as spam in their email client (Gmail, Yahoo, Outlook, etc.), the ISP sends a complaint notification via the feedback loop. Kraiter processes these complaints automatically:
- The complaint notification arrives via SES webhook
- The contact is immediately suppressed with reason
complaint - All active sequence enrolments for the contact are cancelled
- An
email.complainedsystem event is created
Complaints are a serious signal. Even a small number of complaints can affect your sender reputation. Monitor your complaint rate and keep it below 0.1% of delivered emails.
Delivery notifications
You can list recent sends and filter by status to monitor delivery:
// Get recent bounces
const bounces = await kraiter.sends.list({
status: 'bounced',
limit: 50,
});
// Get recent complaints
const complaints = await kraiter.sends.list({
status: 'complained',
limit: 50,
});curl "https://api.kraiter.com/sends?status=bounced&limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"Retry behaviour
Kraiter and SES handle retries at different levels:
SES-level retries
For soft bounces, SES automatically retries delivery for up to 12 hours. You do not need to manage this — it happens transparently.
Kraiter-level retries
If Kraiter cannot submit an email to SES (e.g. due to a temporary SES API error), it retries the submission with exponential backoff. The email remains in queued state during retries.
If all retries are exhausted, the send is marked as failed. This is rare and typically indicates a configuration issue or SES service disruption.
What is not retried
- Hard bounces are never retried — the address is permanently undeliverable
- Complaint-suppressed contacts are never retried
- Emails blocked due to unsubscribe are not retried (the contact opted out)
Monitoring delivery health
Keep an eye on these key metrics to maintain good delivery health:
Bounce rate
Your bounce rate should stay below 5%. A higher rate signals list quality problems:
const metrics = await kraiter.metrics.tenant({
period: 'daily',
from: '2025-06-01',
to: '2025-06-30',
});
for (const day of metrics.items) {
const bounceRate = day.sent > 0 ? (day.bounced / day.sent * 100).toFixed(2) : '0.00';
if (parseFloat(bounceRate) > 5) {
console.warn(`High bounce rate on ${day.date}: ${bounceRate}%`);
}
}Complaint rate
Keep complaints below 0.1% of delivered emails. Above this threshold, ISPs may throttle or block your sends.
Domain health
Check your domain health status regularly:
const domains = await kraiter.domains.list();
for (const domain of domains.items) {
if (domain.health !== 'healthy') {
console.warn(`Domain ${domain.domain} health: ${domain.health}`);
}
}See the Domains guide for more on domain health monitoring.
Best practices
- Clean your contact list regularly. Remove contacts who consistently bounce. Stale lists are the primary cause of high bounce rates.
- Use double opt-in. Verify email addresses before adding contacts to your active list. This dramatically reduces bounces.
- Monitor complaints daily. A spike in complaints needs immediate investigation. Check your recent sends for content or targeting issues.
- Warm up new domains. When you start sending from a new domain, gradually increase volume over several weeks. Sudden high-volume sending from a new domain triggers spam filters.
- Do not send to purchased lists. Purchased email lists have high bounce and complaint rates. Build your contact list organically.
- Check send status for critical emails. For important transactional emails (password resets, account confirmations), check the send status and implement fallback mechanisms if delivery fails.