Stop sending admin emails on every HTTP 500 in production#1356
Conversation
Django's DEFAULT_LOGGING routes the "django" logger to AdminEmailHandler
("mail_admins"), which sends a synchronous outbound email for each HTTP 500.
Override it to console-only so the AdminEmailHandler is bypassed. Sentry
already captures all ERROR-level records independently via LoggingIntegration,
so no observability is lost.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for antenna-ssec ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for antenna-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reached
Next review available in: 58 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds ChangesAdmin Error Email Logging Toggle
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adjusts production Django logging configuration to prevent synchronous admin-error emails on HTTP 500s (intended to avoid email storms during incidents), while keeping error visibility via Sentry and stdout logging.
Changes:
- Introduces
DJANGO_ADMIN_ERROR_EMAILSenv var (defaultFalse) to control admin-error emailing behavior. - When disabled, modifies production
LOGGINGto route Django logs to console and prevent propagation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mihow
left a comment
There was a problem hiding this comment.
Claude says: (takeaway review, drafted by Claude on Michael's behalf.)
The fix is correct and safe to ship. I traced it in a running container rather than from the diff alone, and the mechanism holds up:
- The admin error emails aren't wired anywhere in the repo — they come from Django's
DEFAULT_LOGGING, whichconfigure_logging()applies before this module'sLOGGING. Both usedisable_existing_loggers: False, so thedjangologger silently keeps its defaultmail_adminshandler unless something redefines it. This PR redefines it, which is exactly the right lever, and explains why grepping formail_adminsfinds nothing while the emails still send. - No loss of error visibility.
django.request(where unhandled 500s are logged) has no handlers of its own and propagates up into thedjangologger, so it still reaches the console. Sentry is entirely unaffected:LoggingIntegrationpatcheslogging.Logger.callHandlersupstream of handler dispatch, so it captures the error regardless of the handler list. Console logging is preserved, and there's no double-logging.
One structural point worth addressing before merge (inline suggestion below): the "on" path is implicit. When DJANGO_ADMIN_ERROR_EMAILS=True, the code does nothing and inherits mail_admins from Django's internals — so "on" works only as long as Django's DEFAULT_LOGGING keeps attaching that handler, and nobody reading production.py can see where the emails come from. Making it an explicit branch costs a few lines.
Nothing else needs changing — the fix does what it says.
When DJANGO_ADMIN_ERROR_EMAILS=True the mail_admins handler comes from Django's DEFAULT_LOGGING, not this file. An explicit else branch makes that visible at the site of the toggle instead of hidden in Django's defaults. Co-Authored-By: Claude <noreply@anthropic.com>

Summary
In production, every uncaught HTTP 500 triggers Django's default admin-error email, sent synchronously inside the request — one outbound email per error, each holding a worker thread for the duration of the send. During an error storm this becomes an email storm that ties up the worker pool and makes an incident worse. Sentry already captures every error independently, so these emails add no observability. This change disables that handler by default in deployed environments and puts it behind an environment variable, so it can be re-enabled per environment if a non-blocking (async/queued) handler is configured later.
List of Changes
mail_adminsAdminEmailHandlerbehindDJANGO_ADMIN_ERROR_EMAILS, defaultFalse; when off, thedjangologger routes to console only atINFOso 4xx/5xx request lines still reach stdout, and no longer propagates to the handler.)DJANGO_ADMIN_ERROR_EMAILS=Trueto re-enable admin error email where wanted. Covers production, demo, and staging (same settings module).Notes
LoggingIntegration, independent of theLOGGINGhandlers list.Summary by CodeRabbit