feat: add email warmup send limits for connected accounts (#206)#452
feat: add email warmup send limits for connected accounts (#206)#452Kishalll wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (51)
📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds email warmup rate-limiting to ChangesEmail Warmup Send Limits
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/campaigns/models.py`:
- Around line 42-45: The new persisted warmup fields on ConnectedEmailAccount in
models.py are not backed by a schema migration, so add a migration under
backend/campaigns/migrations that introduces warmup_enabled,
daily_sending_limit, current_daily_count, and warmup_started_at. Use the
existing ConnectedEmailAccount model as the reference point and ensure the
migration matches the field types/defaults currently defined in the model so
deployments stay in sync.
In `@backend/campaigns/tasks.py`:
- Around line 632-635: The failure handler in the task that updates
ConnectedEmailAccount current_daily_count can underflow if the midnight reset
already cleared the counter, so guard the decrement before applying the F()
update. In the failure path around account.warmup_enabled, check the current
value on the account model or use a conditional update in the same
ConnectedEmailAccount.objects.filter(...) call so current_daily_count never goes
below zero, then continue restoring next_execution_time as before.
- Around line 592-604: Make the warmup limit check and counter reservation
atomic in the task logic around account.warmup_enabled so concurrent workers
cannot overshoot daily_sending_limit. Update the flow in the task method that
handles the current_daily_count check to reserve a sending slot with a single
conditional ConnectedEmailAccount.objects.filter(...).update(...) using the
limit predicate, then branch on whether the update succeeded; if it did not,
keep the existing retry/next_execution_time behavior, and if it did, proceed
with sending. Ensure the logger message and retry path remain in the same warmup
handling block.
- Line 601: The retry handling in the task is broken because send_email_step is
unbound, so self.retry cannot be used safely and Celery’s Retry may be swallowed
by the broad exception handler. Bind send_email_step so it can call retry
correctly, and add an explicit except Retry: raise before the generic except
Exception block so the retry propagates instead of being caught.
- Around line 896-900: The warmup update in
ConnectedEmailAccount.objects.filter(...).update() is still allowing
daily_sending_limit to exceed the configured cap because the Case only preserves
values already at or above max_daily and otherwise always adds 5. Update the
logic in the warmup task to clamp the result so it never goes past
WARMUP_MAX_DAILY, using the existing daily_sending_limit field and the
max_daily/WARMUP_MAX_DAILY symbols to locate the affected update expression.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 546d7edf-ee74-444c-a38d-88c6f24940f9
⛔ Files ignored due to path filters (51)
backend/backend/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/backend/__pycache__/celery.cpython-314.pycis excluded by!**/*.pycbackend/backend/__pycache__/settings.cpython-314.pycis excluded by!**/*.pycbackend/backend/__pycache__/urls.cpython-314.pycis excluded by!**/*.pycbackend/backend/__pycache__/wsgi.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/ai.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/apps.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/gmail_service.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/google_auth_views.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/models.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/serializers.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/tasks.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/tests.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/__pycache__/views.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/migrations/__pycache__/0001_initial.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/migrations/__pycache__/0002_campaignlead_last_sent_message_id_and_more.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/migrations/__pycache__/0003_alter_sequencestep_channel_type.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/migrations/__pycache__/0004_connectedemailaccount_connected_by.cpython-314.pycis excluded by!**/*.pycbackend/campaigns/migrations/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/config/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/config/__pycache__/settings.cpython-314.pycis excluded by!**/*.pycbackend/config/__pycache__/urls.cpython-314.pycis excluded by!**/*.pycbackend/config/__pycache__/wsgi.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/apps.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/models.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/serializers.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/tasks.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/tests.cpython-314.pycis excluded by!**/*.pycbackend/leads/__pycache__/views.cpython-314.pycis excluded by!**/*.pycbackend/leads/migrations/__pycache__/0001_initial.cpython-314.pycis excluded by!**/*.pycbackend/leads/migrations/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/admin.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/apps.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/middleware.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/models.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/security.cpython-314.pycis excluded by!**/*.pycbackend/tenants/__pycache__/tests.cpython-314.pycis excluded by!**/*.pycbackend/tenants/migrations/__pycache__/0001_initial.cpython-314.pycis excluded by!**/*.pycbackend/tenants/migrations/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/apps.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/jwt.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/models.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/serializers.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/tests.cpython-314.pycis excluded by!**/*.pycbackend/users/__pycache__/views.cpython-314.pycis excluded by!**/*.pycbackend/users/migrations/__pycache__/0001_initial.cpython-314.pycis excluded by!**/*.pycbackend/users/migrations/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pyc
📒 Files selected for processing (5)
.gitignorebackend/backend/settings.pybackend/campaigns/models.pybackend/campaigns/tasks.pybackend/db.sqlite3
…#206) - Add warmup_enabled, daily_sending_limit, current_daily_count, warmup_started_at fields to ConnectedEmailAccount model - Add warmup guard in send_email_step: blocks sends when daily limit reached, retries in 1 hour, uses F() for atomic increment - Set warmup_started_at on first warmup trigger - Guard decrement on send failure to prevent negative count - Add warmup_daily_reset periodic task: resets daily counts and increments limits by +5/day (capped at WARMUP_MAX_DAILY) - Register midnight UTC beat schedule for daily reset - Add WARMUP_MAX_DAILY setting (default 100, env-configurable) - Fix .gitignore: remove corrupted null-byte entries, untrack __pycache__ files and db.sqlite3
b3c083f to
61fd5c4
Compare
|
@Kuldeeep18 pls review |
🔗 Related Issue
Closes #206
📝 Summary of Changes
Implemented email warmup send limits for connected email accounts to prevent new accounts from triggering spam filters by immediately sending hundreds of emails.
Core changes:
warmup_enabled,daily_sending_limit,current_daily_count, andwarmup_started_atfields to theConnectedEmailAccountmodelsend_email_stepCelery task that blocks sends when the daily limit is reached and retries after 1 hourwarmup_daily_resetperiodic task that runs at midnight UTC to reset daily send counts and increment warmup limits by +5/day (capped atWARMUP_MAX_DAILY)F()expressions for atomic increment/decrement to prevent race conditions with concurrent Celery workersWARMUP_MAX_DAILYsetting (default 100, configurable via env var)Cleanup:
.gitignore(had null bytes preventing proper ignore rules)__pycache__files anddb.sqlite3from git tracking🏷️ Type of Change
🧪 Testing
Steps to test:
python manage.py check— should pass with no issuespython manage.py test campaigns— 53 tests pass (1 pre-existing Django/Python 3.14 compatibility error unrelated to this change)current_daily_countreachesdaily_sending_limitwarmup_daily_resettask resets counts and increments limits correctly📸 Screenshots (if applicable)
N/A — backend-only change, no UI modifications.
✅ Checklist
Note: The migration file (
0011_connectedemailaccount_current_daily_count_and_more.py) was not included in this commit. It needs to be generated and applied during deployment viapython manage.py makemigrations campaigns && python manage.py migrate.Summary by CodeRabbit