feat: add resend notification endpoint#2
Conversation
Adds GET /admin/resend?since=<ISO8601> endpoint that lists all files in the monitored Dropbox folder modified after the given timestamp and re-sends the notification email with those files as attachments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✨ 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 |
Isolates the Dropbox folder scanning, shared link resolution, file filtering and download logic into a private _resend method, keeping the route handler focused on validation and email sending. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ed90b6c5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| entry | ||
| for entry in entries | ||
| if entry.get(".tag") == "file" | ||
| and entry.get("server_modified", "") >= since |
There was a problem hiding this comment.
Parse
since as datetime before filtering entries
The filter compares entry["server_modified"] and the query since as raw strings, which is not a reliable chronological comparison for valid ISO8601 inputs that use different representations (for example timezone offsets like -05:00 or fractional seconds). In those cases this endpoint can resend files outside the requested time window or miss files that should be included, even though the input timestamp is valid.
Useful? React with 👍 / 👎.
| appier_extras.admin.Base.send_email_g( | ||
| owner, | ||
| "email/updated.html.tpl", | ||
| receivers=receivers if receivers else [email], |
There was a problem hiding this comment.
Avoid sending "folder updated" emails with no changes
This call is unconditional, so requests where no files match since still send an "updated" email with an effectively empty body (added_entries=[], removed_entries=[]). That creates false notifications and can spam recipients during routine backfill/retry calls; the handler should return resent=0 without sending when there are no matching entries.
Useful? React with 👍 / 👎.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an authenticated admin-only endpoint to re-trigger Dropbox notification emails for files modified after a provided timestamp, reusing the existing email + attachment flow used by the scheduler.
Changes:
- Add
GET /admin/resend?since=<ISO8601>endpoint under the admin controller. - Resolve shared link + enumerate folder entries, filter by modification time, download matching files, and send “folder updated” email with attachments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if share_links: | ||
| shared = share_links[0] | ||
|
|
||
| # loops trying to find the best possible share link | ||
| # for the folder, keeping in mind that using extended sharing | ||
| # controls will allows deep shared folder | ||
| for share_link in share_links: | ||
| link_permissions = share_link.get("link_permissions", {}) | ||
| if not link_permissions.get("can_use_extended_sharing_controls", False): |
There was a problem hiding this comment.
Filtering with entry.get("server_modified", "") >= since compares ISO8601 timestamps as raw strings. This can yield incorrect results when since uses a different timezone/format (e.g., +00:00 vs Z) or includes fractional seconds, and it also accepts invalid since values silently. Parse/validate since and server_modified as datetimes (normalize to UTC) and raise an OperationalError for invalid since formats before filtering.
| shared = share_link | ||
|
|
||
| # creates a shared link to the folder so that it can be used | ||
| # for the URL creation in no shared link already exists | ||
| else: | ||
| shared = api.create_shared_link(folder_path) | ||
|
|
||
| shared_url = appier.legacy.urlparse(shared["url"]) | ||
| shared_base = f"{shared_url.scheme}://{shared_url.netloc}{shared_url.path}" | ||
| shared_query = shared_url.query | ||
|
|
There was a problem hiding this comment.
This endpoint can download and attach every file modified since an arbitrary timestamp. If since is far in the past (or the folder has many/large files), this may cause long request times, high Dropbox API usage, large memory consumption, and oversized emails. Consider adding a hard cap (max files and/or total bytes), returning a summary when the cap is exceeded, and/or providing a way to send links instead of attachments for large payloads.
| added_entries, added_files, folder_path, shared_base, shared_query, prefix_size = ( | ||
| self._resend(folder_path, since=since) | ||
| ) | ||
|
|
||
| appier_extras.admin.Base.send_email_g( | ||
| owner, | ||
| "email/updated.html.tpl", | ||
| receivers=receivers if receivers else [email], | ||
| cc=cc, | ||
| bcc=bcc, | ||
| reply_to=reply_to, | ||
| subject=owner.to_locale(f"Dropbox folder {folder_path} updated"), | ||
| attachments=added_files, | ||
| added_entries=added_entries, | ||
| removed_entries=[], | ||
| folder_path=folder_path, | ||
| folder_url=shared_base, | ||
| folder_query=shared_query, | ||
| prefix_size=prefix_size, | ||
| ) | ||
|
|
||
| return dict(since=since, resent=len(added_files)) | ||
|
|
||
| def _resend(self, folder_path, since=None): | ||
| api = self.get_api() | ||
|
|
||
| folder_meta = api.metadata_file(folder_path) | ||
| folder_path = folder_meta["path_display"] | ||
| prefix_size = len(folder_path) | ||
|
|
||
| share = api.list_shared_links(folder_path) | ||
| share_links = share.get("links", []) |
There was a problem hiding this comment.
resend() duplicates a large block of folder-resolution/shared-link/email composition logic that already exists in Scheduler.scan_folder(). This increases the risk of the two flows drifting (eg. bug fixes applied to one but not the other). Consider extracting the common logic into a shared helper (eg. in a service/module used by both the scheduler and controller) and keep resend() focused on the since filtering behavior.
| removed_entries=[], | ||
| folder_path=folder_path, | ||
| folder_url=shared_base, |
There was a problem hiding this comment.
Grammar in the comment: "controls will allows deep shared folder" should be "controls will allow deep shared folders".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
GET /admin/resend?since=<ISO8601>endpoint to re-trigger notification emails for files modified after a given timestampserver_modified >= sinceand sends the notification email with file attachmentsTest plan
/admin/resend?since=2026-03-01T00:00:00Zwith valid admin credentialsOperationalErroris raised whensinceparameter is missingOperationalErroris raised whenNOTIFIER_FOLDERis not configured🤖 Generated with Claude Code