DEV Community

Jonathan
Jonathan

Posted on

GitHub Actions Logs for Email API Retries

Email API tests often look healthy right until a workflow turns flaky. The send step passes, the poller retries a few times, and then the job finally fails with a vague timeout. What is missing is not more retry logic. It is a better record of what each retry actually saw.

I have found that GitHub Actions gets much easier to trust when retry attempts are treated like artifacts instead of throwaway console noise. This is specially useful when a workflow creates a disposable temporary email for a signup or verification path, because the inbox state changes across a short window and the timing matters a lot.

Why email API retries become invisible in CI

Many teams already log the final failure, but they do not log the sequence that led there. That creates a weird debugging gap:

  • attempt 1 may have queried the wrong inbox
  • attempt 2 may have matched the right inbox but the wrong subject
  • attempt 3 may have found the message after the expiry window
  • the final job summary only says "message not found"

That pattern burns time because engineers end up re-running the workflow just to get fresh clues. The same idea shows up in stable inbox checks: flakiness usually gets smaller once the workflow records exactly what it was trying to prove.

One more thing I keep seeing is placeholder drift. A local helper might still mention temp mailid or tempail mail in a fixture name, while the CI step now uses a real inbox provider or a different address contract. The mismatch sounds tiny, but it makes logs harder to scan when you are already tired.

Log attempts as a first-class workflow artifact

The most useful upgrade is boring: write one JSON line per poll attempt, then upload the file even on failure.

{"attempt":1,"elapsed_ms":0,"status":"empty","inbox":"signup-1842"}
{"attempt":2,"elapsed_ms":3500,"status":"empty","inbox":"signup-1842"}
{"attempt":3,"elapsed_ms":7100,"status":"matched","subject":"Verify your email"}
Enter fullscreen mode Exit fullscreen mode

That file gives you a timeline, not just a verdict. It also means you can keep console output short while preserving the details that matter.

I like pairing that with a markdown summary written to $GITHUB_STEP_SUMMARY:

{
  echo "## Email retry summary"
  echo "- inbox: $INBOX_ID"
  echo "- attempts: $ATTEMPTS"
  echo "- final state: $FINAL_STATE"
  echo "- artifact: retry-log.jsonl"
} >> "$GITHUB_STEP_SUMMARY"
Enter fullscreen mode Exit fullscreen mode

Now the person opening the run gets the short story first, and the artifact second. That split is realy nice for on-call work because you do not have to read 500 lines before deciding whether the failure was expected noise or a real regression.

A small GitHub Actions pattern that helps fast

You do not need a big framework to do this. A few explicit steps are enough:

  1. create the inbox contract
  2. trigger the API that should send the message
  3. poll on a fixed schedule
  4. append each attempt to a retry log
  5. publish the summary and upload the artifact

The shell flow can stay small:

RUN_DIR="artifacts/${GITHUB_RUN_ID}"
mkdir -p "$RUN_DIR"

./scripts/create-inbox.sh > "$RUN_DIR/inbox.json"
./scripts/send-verification.sh --inbox "$RUN_DIR/inbox.json"
./scripts/poll-email.sh --inbox "$RUN_DIR/inbox.json" --log "$RUN_DIR/retry-log.jsonl"
echo "- retry artifact: retry-log.jsonl" >> "$GITHUB_STEP_SUMMARY"
Enter fullscreen mode Exit fullscreen mode

GitHub's artifact docs are worth using here because retention and sharing are already built in: https://docs.github.com/actions/using-workflows/storing-workflow-data-as-artifacts. If you have ever had to explain a failed workflow in chat, artifact links are a much cleaner handoff than pasted logs.

This is also why I like replay logs for automation. The principle is the same: if a tool makes decisions over time, capture the trail in a format another engineer can replay mentally in under a minute.

What to record for a disposable temporary email check

For most APIs, I do not think you need the full email body in your retry log. A lean record is better:

  • inbox or scenario ID
  • attempt number
  • elapsed time
  • matched subject, if any
  • message timestamp, if any
  • terminal reason for stop

That gives enough context to see whether the retry budget is wrong, the send path is slow, or the lookup filter is too loose. If the workflow is testing link parsing or token extraction, then yes, add a richer artifact for that case only.

One practical tip: keep retry intervals fixed inside the log output. Exponential backoff is fine for production clients, but in CI it can make failures harder to compare across runs. Consistent intervals are less clever, but way easier to debug and that tradeoff is usualy worth it.

Q&A

Should every failed poll attempt be printed to the main log?

No. Put the details in an artifact and keep the main log readable. The workflow summary should point to the artifact.

Is this only for signup tests?

No. Password reset, email change, team invite, and approval flows all benefit from the same pattern.

What is the fastest win if a team is short on time?

Start with one retry-log.jsonl artifact and one short summary block. That alone removes a lot of guesswork from GitHub Actions runs.

Top comments (0)