DEV Community

jasonmills94
jasonmills94

Posted on

EKS CI: Keep Test Mailbox Failures Actionable

EKS CI: Keep Test Mailbox Failures Actionable

Email verification is a small step in many end-to-end tests, but in an EKS pipeline it can become the least observable part of the run. A pod waits for a message, the message arrives late, and the test retries until the whole job is marked flaky. The team then sees a red build without knowing whether the problem was the application, Kubernetes scheduling, the mail provider, or a test fixture that was reused by another worker.

The fix I prefer is to treat a test mailbox like a leased infrastructure resource. Give it an owner, a deadline, and an evidence trail. That makes a failed email check actionable instead of just annoying.

The failure mode: an email check that hides the real CI failure

A common test sequence looks simple:

  1. Create a user with a temporary address.
  2. Submit the signup form.
  3. Poll for a verification message.
  4. Open the link and finish the flow.

In a local run, one worker and one mailbox can make this feel reliable. In a Kubernetes CI job, several workers may share a namespace, a NAT gateway, DNS, and the same mailbox pool. A slow consumer can read a message intended for a different test. A restarted pod can lose the polling cursor. A short-lived job can disappear before its final diagnostic is flushed.

The resulting error is often just verification email not found. That message has very little operational value. It also make developers add another retry, which increases the time before anyone looks at the real cause.

A mailbox lease as a Kubernetes resource

For each test case, create a lease record with these fields:

  • run_id: the CI workflow and attempt identifier
  • worker_id: the pod or test shard that owns the mailbox
  • mailbox_id: the disposable address or provider-side identifier
  • created_at and expires_at: the lifetime of the fixture
  • expected_message_id: a correlation value generated before signup
  • state: reserved, message_seen, verified, or expired

The lease does not need to be a custom Kubernetes object. A row in a small test-control service or a namespaced key in Redis can work. The important part is that reservation and cleanup are explicit. Do not let a test infer ownership from an address alone.

I use a short lease for normal tests and renew it only while the test is making progress. If the pod is evicted, the lease expires naturally. A later worker can see that the resource is stale instead of waiting on an inbox that nobody owns anymore.

For privacy, keep message content out of ordinary application logs. The privacy checks for disposable email workflows are a useful companion when deciding what the test service may retain.

Design the evidence path before adding retries

Retries should recover a transient condition, not erase it. Each poll should record structured facts such as:

{
  "run_id": "gha-1842-3",
  "mailbox_id": "mbx-7f2",
  "attempt": 4,
  "elapsed_ms": 18200,
  "lease_remaining_ms": 41800,
  "message_count": 0,
  "worker": "checkout-6c8d9d7f9b-q4k2m"
}
Enter fullscreen mode Exit fullscreen mode

The log should contain identifiers, timings, and state transitions, but not verification tokens or full email bodies. When a poll times out, the test can distinguish “no message was delivered” from “a message was delivered but the worker could not claim it.” Those are different incidents and need different owners.

It is tempting to use a generic label like temp org mail in a dashboard and stop there. That label is not enough for debugging. Pair it with the run, shard, and lease state so an engineer can trace one fixture from reservation through cleanup.

A practical EKS implementation

Run the test controller as a small deployment in the same EKS cluster, but keep the mailbox provider behind a narrow service interface. Test pods request a lease through the controller instead of talking to provider APIs directly. This gives the platform team one place to enforce quotas, redact data, and release abandoned fixtures.

Use a Kubernetes ServiceAccount with only the permissions the controller needs. It usually does not need permission to list every pod in the cluster. If pod identity is required, pass the worker ID from the test runner and verify it against the request metadata rather than granting broad cluster access.

For CI jobs, attach a run_id label to the test pod and include the same value in the lease. Export controller metrics for:

  • lease reservations and expirations
  • message delivery latency
  • verification completion latency
  • claims rejected because of ownership
  • cleanup failures

Then route those signals with the rest of the deployment and rollback signals. The notes on signal routing during EKS rollbacks apply here too: a useful alert says what changed, which run is affected, and what action is safe.

What to alert on

Do not alert on one missing message. Alert on a pattern that indicates a boundary failure:

  • delivery latency is above the test budget for several runs
  • lease expirations increase while application requests remain healthy
  • ownership conflicts appear on one shard or namespace
  • cleanup failures leave fixtures past their retention window
  • the controller cannot reach the provider, but tests continue retrying

An alert should link to the CI run and the lease evidence. If the controller is unavailable, fail fast with that reason rather than letting hundreds of test pods poll until their deadlines.

Short checklist for the next pipeline

  • Reserve one mailbox lease per test owner.
  • Put the lease deadline below the CI job timeout.
  • Correlate the signup request, message, and verification step.
  • Keep tokens and message bodies out of logs.
  • Make retries bounded and state-aware.
  • Emit cleanup metrics even when the test fails.
  • Test controller failure separately from application failure.

Questions I ask before shipping

Can two workers claim the same message? If yes, the fixture boundary is incomplete.

What happens after a pod restart? The lease should be recoverable or expire, not remain in an unknown state.

Can an engineer explain a failure from one CI link? If not, add correlation fields before adding more retries.

Does cleanup run when the test is cancelled? It should be best-effort, observable, and safe to repeat.

The useful outcome is not a mailbox that never fails. External delivery has latency and outages. The useful outcome is a pipeline that can say exactly which boundary failed, preserve enough evidence to investigate, and release its temporary resources when the run is over.

Top comments (0)