From dc73cfdde907c805e03c4e6b3364df0e96261c59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:03:59 +0000 Subject: [PATCH 1/4] Initial plan From 3f38d5ec4dbafdd5501a0c1b1493ea5f6a5106e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:07:01 +0000 Subject: [PATCH 2/4] Convert resend-list endpoint from GET to POST with JSON body to avoid URL length limits Co-authored-by: gregv <6913307+gregv@users.noreply.github.com> --- api/volunteers/volunteers_views.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/api/volunteers/volunteers_views.py b/api/volunteers/volunteers_views.py index a2a8cfe..8c8fc8d 100644 --- a/api/volunteers/volunteers_views.py +++ b/api/volunteers/volunteers_views.py @@ -659,13 +659,21 @@ def admin_get_resend_email_statuses(): return _error_response(f"Failed to fetch email statuses: {str(e)}") -@bp.route('/admin/emails/resend-list', methods=['GET']) +@bp.route('/admin/emails/resend-list', methods=['POST']) @auth.require_org_member_with_permission("volunteer.admin", req_to_org_id=getOrgId) def admin_list_resend_emails(): """Admin endpoint to list all sent emails from Resend, indexed by recipient.""" try: - emails_param = request.args.get('emails', '') - filter_emails = [e.strip() for e in emails_param.split(',') if e.strip()] if emails_param else None + body = request.get_json(silent=True) or {} + emails_param = body.get('emails', []) + if isinstance(emails_param, str): + parsed = [e.strip() for e in emails_param.split(',') if e.strip()] + filter_emails = parsed if parsed else None + elif isinstance(emails_param, list): + parsed = [e.strip() for e in emails_param if isinstance(e, str) and e.strip()] + filter_emails = parsed if parsed else None + else: + filter_emails = None logger.info("Listing Resend emails, filter_count=%d", len(filter_emails) if filter_emails else 0) From 619b6617df6da2a4139548206ae3afc612162694 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:08:12 +0000 Subject: [PATCH 3/4] Initial plan From eb496d6fcabbe6fd05549ea7e6d1b316f495e7cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 05:11:56 +0000 Subject: [PATCH 4/4] Fix list_all_resend_emails: pagination cap, error handling, empty-result caching Co-authored-by: gregv <6913307+gregv@users.noreply.github.com> --- services/volunteers_service.py | 41 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/services/volunteers_service.py b/services/volunteers_service.py index ed5e3d0..cd1a923 100644 --- a/services/volunteers_service.py +++ b/services/volunteers_service.py @@ -2028,13 +2028,17 @@ def list_all_resend_emails(filter_emails=None): 'success': True, 'emails_by_recipient': index, 'total_fetched': cached['total_fetched'], + 'truncated': cached.get('truncated', False), 'from_cache': True } # Paginate through resend.Emails.list() (requires resend >= 2.5) + # Safety cap of 100 pages (~10,000 emails); for-else detects if we hit it. all_emails = [] - max_pages = 10 + max_pages = 100 params = {"limit": 100} + page_error_occurred = False + truncated = False for page in range(max_pages): try: @@ -2058,7 +2062,22 @@ def list_all_resend_emails(filter_emails=None): except Exception as page_error: warning(logger, "Error fetching Resend emails page", page=page, exc_info=page_error) + page_error_occurred = True break + else: + # Loop exhausted max_pages without a natural break — results are truncated. + truncated = True + warning(logger, "Resend email pagination hit safety cap", + max_pages=max_pages, total_so_far=len(all_emails)) + + # If a page fetch failed, return an error rather than caching partial data. + if page_error_occurred: + return { + 'success': False, + 'error': 'Failed to fetch all email pages from Resend', + 'emails_by_recipient': {}, + 'total_fetched': len(all_emails) + } # Build index by recipient email index = {} @@ -2094,16 +2113,17 @@ def list_all_resend_emails(filter_emails=None): total_fetched = len(all_emails) - # Cache the full index with 300s TTL - # Only cache if we actually fetched emails (avoid caching errors/empty results) - if total_fetched > 0: - set_cached(cache_key, { - 'emails_by_recipient': index, - 'total_fetched': total_fetched - }, ttl=300) - info(logger, "Built Resend email index", - total_fetched=total_fetched, unique_recipients=len(index)) + total_fetched=total_fetched, unique_recipients=len(index), + truncated=truncated) + + # Cache the full index with 300s TTL, including empty results so we + # don't hammer the Resend API on every request when there are no emails. + set_cached(cache_key, { + 'emails_by_recipient': index, + 'total_fetched': total_fetched, + 'truncated': truncated + }, ttl=300) # Filter if requested if filter_emails: @@ -2114,6 +2134,7 @@ def list_all_resend_emails(filter_emails=None): 'success': True, 'emails_by_recipient': index, 'total_fetched': total_fetched, + 'truncated': truncated, 'from_cache': False }