Skip to content

Rate Limiting

Client-side rate limiting protects against sending too many messages from a single inbox or to a single recipient. Limits use a sliding time window and are enforced on all send operations — sendMessage, replyToMessage, replyAllToMessage, and forwardMessage.

Per-Sender Limiting

Tracks messages by sender inbox ID. Each inbox has its own independent counter:

suspend fun perSenderExample() {
    val client = AgentMailClient {
        perSenderRateLimiter {
            maxMessages = 10
            window = 60.seconds
            onLimitExceeded = RateLimitAction.STOP
        }
    }

    // Each inbox is tracked independently — "inbox-a" and "inbox-b"
    // each get their own 10-message window
    client.sendMessage {
        from = "inbox-a"
        to = listOf("user@example.com")
        subject = "From inbox A"
        text = "This counts toward inbox-a's limit."
    }

    client.sendMessage {
        from = "inbox-b"
        to = listOf("user@example.com")
        subject = "From inbox B"
        text = "This counts toward inbox-b's limit."
    }

    client.close()
}

Per-Recipient Limiting

Tracks messages by recipient email address. Each address in to, cc, and bcc is checked independently:

suspend fun perRecipientExample() {
    val client = AgentMailClient {
        perRecipientRateLimiter {
            maxMessages = 3
            window = 30.seconds
            onLimitExceeded = RateLimitAction.SKIP
        }
    }

    // Each recipient address is tracked independently
    val response = client.sendMessage {
        from = "inbox-id"
        to = listOf("alice@example.com", "bob@example.com")
        subject = "Update"
        text = "Both recipients are checked against their own limits."
    }

    if (response == null) {
        println("Message skipped — a recipient hit their rate limit")
    }

    client.close()
}

Actions

When a limit is exceeded, the configured action determines what happens:

Action Behavior
STOP Throws RateLimitExceededException (default)
SKIP Returns null without sending
DELAY Suspends until the window clears, then sends

DELAY — Automatic Throttling

Use DELAY to automatically pace outgoing messages without dropping or failing:

suspend fun delayExample() {
    val client = AgentMailClient {
        perSenderRateLimiter {
            maxMessages = 5
            window = 10.seconds
            onLimitExceeded = RateLimitAction.DELAY
        }
    }

    // Sends 10 messages — the first 5 go immediately,
    // then the coroutine suspends until the window clears
    for (i in 1..10) {
        client.sendMessage {
            from = "inbox-id"
            to = listOf("recipient@example.com")
            subject = "Message #$i"
            text = "Automatically throttled to 5 per 10 seconds."
        }
        println("Sent message #$i")
    }

    client.close()
}

STOP — Fail Fast

Use STOP to immediately halt when a limit is hit. Catch RateLimitExceededException to handle it:

suspend fun stopExample() {
    val client = AgentMailClient {
        perSenderRateLimiter {
            maxMessages = 2
            window = 10.seconds
            onLimitExceeded = RateLimitAction.STOP
        }
    }

    try {
        repeat(5) { i ->
            client.sendMessage {
                from = "inbox-id"
                to = listOf("recipient@example.com")
                subject = "Message #${i + 1}"
                text = "This will throw on the 3rd attempt."
            }
        }
    } catch (e: RateLimitExceededException) {
        println("Blocked: ${e.message}")
        // Blocked: Rate limit exceeded for 'inbox-id' (2 per 10s)
    }

    client.close()
}

SKIP — Silent Drop

Use SKIP when it's acceptable to silently drop excess messages. Check for a null return to detect skipped sends — see the per-recipient example above.

Combining Both Limits

Per-sender and per-recipient limits can be used together. The sender limit is checked first, then each recipient:

suspend fun combinedExample() {
    val client = AgentMailClient {
        // Limit each inbox to 20 messages per minute
        perSenderRateLimiter {
            maxMessages = 20
            window = 1.minutes
            onLimitExceeded = RateLimitAction.DELAY
        }

        // Limit each recipient to 3 messages per minute
        perRecipientRateLimiter {
            maxMessages = 3
            window = 1.minutes
            onLimitExceeded = RateLimitAction.STOP
        }
    }

    // Both limits are checked: sender limit first, then each recipient
    client.sendMessage {
        from = "inbox-id"
        to = listOf("recipient@example.com")
        subject = "Hello"
        text = "Protected by both sender and recipient limits."
    }

    client.close()
}

Replies and Forwards

Rate limiting applies to all send operations, not just sendMessage:

suspend fun replyRateLimited() {
    val client = AgentMailClient {
        perSenderRateLimiter {
            maxMessages = 5
            window = 30.seconds
            onLimitExceeded = RateLimitAction.SKIP
        }
    }

    val messages = client.listMessages("inbox-id")
    val message = messages.messages.first()

    // Replies and forwards are also rate-limited
    val response = client.replyToMessage(message) {
        text = "Thanks for reaching out!"
    }

    if (response == null) {
        println("Reply skipped — sender rate limit reached")
    }

    client.close()
}

Next Steps