feat: sponsor blast endpoint with pitch engine and rate limiting #325
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api#325
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Feature
Lineage
Child of basketball-api#316 (sponsor outreach system). Blocked by: #321 (model + CRUD). Depends on: #320 (MJML template must exist on disk).
Repo
forgejo_admin/basketball-apiUser Story
As an admin,
I want to blast sponsor outreach emails filtered by category and track who was contacted,
So that I can run targeted campaigns and follow up with non-responders.
Context
This ticket adds the blast endpoint and pitch engine on top of the Sponsor model (created in #321). It sends branded emails using the sponsor-outreach.html template (#320) via the existing
load_email_template()+get_gmail_client()infrastructure.Key behaviors:
custom_pitchoverrideFile Targets
Files the agent should modify:
src/basketball_api/services/sponsor_service.py— add blast_sponsors(), get_pitch(), CATEGORY_PITCHES dictsrc/basketball_api/routes/sponsors.py— add POST /sponsors/blast endpointFiles the agent should create:
tests/test_sponsor_blast.py— blast-specific testsFiles the agent should reference (read, not modify):
src/basketball_api/services/email.py— import load_email_template, get_gmail_client, EmailLog, EmailTypesrc/basketball_api/models.py— Sponsor, SponsorStatus, SponsorCategory, EmailTypeFiles the agent should NOT touch:
src/basketball_api/services/email.py— do not modify, only import fromsrc/basketball_api/models.py— already modified in #321Acceptance Criteria
CATEGORY_PITCHESdict with defaults for all 9 categories (food, financial, retail, automotive, construction, fitness, dental, grocery, other)get_pitch(sponsor)returnssponsor.custom_pitchif set, otherwiseCATEGORY_PITCHES[sponsor.category]POST /sponsors/blastaccepts: category (optional), status (optional, default "prospect"), include_tiers (bool), test_email (optional)load_email_template("sponsor-outreach", data)for rendering{"sent": N, "failed": N, "sponsors": [{"id": ..., "business_name": ..., "email": ...}]}Category Pitch Defaults
Derived from Marcus's actual GroupMe emails:
Sponsorship Tiers Block (HTML for template)
When include_tiers=true, render this as the
{{sponsorship_tiers}}placeholder:When include_tiers=false,
{{sponsorship_tiers}}= empty string.Test Expectations
pytest tests/test_sponsor_blast.py -vConstraints
time.sleep(3)between sends (not async)Checklist
Related