Skip to content

[_]: feature/attachments#57

Merged
xabg2 merged 13 commits into
masterfrom
feature/attachments
Jun 4, 2026
Merged

[_]: feature/attachments#57
xabg2 merged 13 commits into
masterfrom
feature/attachments

Conversation

@xabg2

@xabg2 xabg2 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Allowing the user to send attachments when composing an email and download attachments from an email. The limit is set to <25MB.

@xabg2 xabg2 self-assigned this Jun 2, 2026
@xabg2 xabg2 added the enhancement New feature or request label Jun 2, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 2, 2026

Copy link
Copy Markdown

Deploying mail-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: a003da7
Status: ✅  Deploy successful!
Preview URL: https://7e6f9cf3.mail-web-ea0.pages.dev
Branch Preview URL: https://feature-attachments.mail-web-ea0.pages.dev

View logs

@xabg2 xabg2 changed the base branch from master to feature/upload-attachment-manager June 3, 2026 11:18
@xabg2 xabg2 changed the base branch from feature/upload-attachment-manager to feature/custom-hook-to-upload-attachments June 3, 2026 13:53
@xabg2 xabg2 marked this pull request as ready for review June 3, 2026 14:00
@xabg2 xabg2 requested a review from jzunigax2 June 3, 2026 14:01
[toRecipients, ccRecipients, bccRecipients],
);

const encryptionState = useMemo<'none' | 'encrypted' | 'cleartext'>(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had added unknown state as well in useComposeSend.ts as that was flagged by coderabbit and actually made sense as it would solve some edge cases. for example when activeDomains is still loading, encryptionState now returns 'none' when !activeDomains so hitting send before that actually skips encryption

Is useComposeSend.ts now dead code?

these are my main concerns, all else relating to uploads looks good

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the custom hook, so we can remove the logic from the component. It should be encapsulated to a custom hook. I will manage the send logic to the custom hook.

@jzunigax2

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds email attachment support end-to-end: users upload files via a compose dialog file picker, the send pipeline includes completed attachments in both cleartext and encrypted email payloads, and attachments are displayed in mail previews with download functionality. SDK is updated to ^1.17.4 with HTTP retry enabled.

Changes

Email Attachment Support

Layer / File(s) Summary
SDK dependency and test infrastructure
package.json, src/services/sdk/index.ts, src/test-utils/fixtures.ts
SDK dependency updated to ^1.17.4; HttpClient.enableGlobalRetry() called at module init. Test utilities add getMockedAttachment() factory and update getMockedMail() to include attachments array when hasAttachment flag is set.
Compose message attachment UI and state management
src/components/compose-message/components/AttachmentList.tsx, src/components/compose-message/hooks/useComposeMessage.ts, src/components/compose-message/index.tsx
New AttachmentList component displays uploaded files with status-dependent icons, size formatting, and retry/remove actions. useComposeMessage adds clear() method to reset compose state. ComposeMessageDialog integrates file picker via useRef, tracks attachment state via useAttachments, wires paperclip button to file input, and disables Send when attachments are uploading or have errors.
Attachment send pipeline integration
src/components/compose-message/hooks/useComposeSend.ts, src/components/compose-message/hooks/useComposeSend.test.ts
useComposeSend accepts attachments: AttachmentTask[] parameter, filters completed attachments (status='done' with blobId), and includes them in both cleartext and encrypted SendEmailRequest payloads. Test helper passes explicit attachments: [] and mock encryption block is reformatted for readability.
Mail preview attachment display and download
src/features/mail/MailView.tsx, src/features/mail/components/mail-preview/index.tsx, src/features/mail/components/mail-preview/preview/index.tsx
MailView passes mail.id to PreviewMail. PreviewMail accepts mail.attachments typed from SDK and forwards to Preview. Preview component renders attachment list with filename/size; clicking downloads via MailService.downloadAttachment(), creates blob URL, triggers browser download, revokes URL, and shows localized error toast on failure.
Localization
src/i18n/locales/en.json, src/i18n/locales/es.json, src/i18n/locales/fr.json, src/i18n/locales/it.json
New translation keys added across all languages: mail.preview.errors.downloadAttachmentFailed for download failures; modals.composeMessageDialog.errors.{noRecipients, sendFailed, keyLookupFailed} for compose validation; modals.composeMessageDialog.attachments.totalSize label for upload UI.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Suggested reviewers

  • jzunigax2
  • larryrider
  • CandelR
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '[_]: feature/attachments' is vague and generic, using a placeholder-like prefix and non-descriptive formatting that does not clearly convey the main change to someone reviewing PR history. Use a clear, descriptive title like 'Add email attachment support for compose and preview' that explains the feature without relying on branch names or placeholder prefixes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly relates to the changeset, explaining that the PR adds attachment sending/downloading functionality with a 25 MB size limit, which aligns with the changes across components, services, and localization files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/attachments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/compose-message/hooks/useComposeSend.test.ts (1)

35-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add tests for attachment filtering and payload inclusion in send.

The helper now injects attachments, but the suite does not verify the new behavior (done + blobId included, other states excluded) in cleartext/encrypted payloads. This leaves the new send-path logic unprotected.

As per coding guidelines: “Test every .ts file that contains logic” and “covering happy path, edge cases, error/rejection paths, and boundary values.”

🤖 Prompt for 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.

In `@src/components/compose-message/hooks/useComposeSend.test.ts` around lines 35
- 50, Update the renderSend test helper and add unit tests for
useComposeSend.send to verify attachment filtering and payload composition:
write tests that call renderSend (which returns result and onSent) and invoke
result.current.send for both cleartext and encrypted flows, mocking attachments
with various states and asserting only attachments with state === "done"
contribute a blobId to the final payload (and others are excluded), and assert
the constructed payload includes the expected blobId fields and that onSent is
called with that payload; reference the renderSend helper, useComposeSend hook,
and the send method on result.current to locate where to add these cases.
🤖 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 `@package.json`:
- Line 27: The package.json dependency entry for "`@internxt/sdk`" uses an
invalid/unpublished version spec "^1.17.4"; update the "`@internxt/sdk`"
dependency to a published version (for example "^1.15.10") or to the actual
latest published tag, then run npm install (or yarn) to refresh node_modules and
the lockfile; verify the change by checking the dependency entry "`@internxt/sdk`"
in package.json and confirming the installed version via npm ls or npm info.

In `@src/components/compose-message/hooks/useComposeMessage.ts`:
- Around line 48-55: Add a unit test in
src/components/compose-message/hooks/useComposeMessage.test.ts that uses
renderHook (and act) to call useComposeMessage, sets non-empty values for
subjectValue, toRecipients, ccRecipients, bccRecipients and toggles
showCc/showBcc via the hook’s exposed setters, then calls result.current.clear()
and asserts that subjectValue === '', toRecipients/ccRecipients/bccRecipients
are empty arrays, and showCc/showBcc are false; reference the hook name
useComposeMessage and the clear() function in the test so it explicitly
exercises and verifies the reset behavior.

In `@src/components/compose-message/index.tsx`:
- Line 62: Move the file-picking logic out of the ComposeMessage component into
the useAttachments hook: take the existing fileInputRef and onFilesPicked
handler out of src/components/compose-message/index.tsx and implement them
inside useAttachments (use the existing addFiles call to add selected files),
expose a triggerFilePicker function and a fileInputProps object (containing ref,
type:'file', multiple:true, hidden:true, onChange handler that calls addFiles
and resets e.target.value). In the component, remove fileInputRef/onFilesPicked
and consume const { triggerFilePicker, fileInputProps } = useAttachments(),
render <input {...fileInputProps} /> and wire the file picker button to
onClick={triggerFilePicker} (preserve disabled logic like isSending). Update any
other occurrences of fileInputRef/onFilesPicked in this file accordingly.

In `@src/features/mail/components/mail-preview/preview/index.tsx`:
- Around line 42-68: The Preview component contains download orchestration
(onDownload) and side effects (calling MailService.instance.downloadAttachment,
creating blobs/URLs, and notificationsService.show) which must be extracted;
create a new hook (e.g., useAttachmentDownloader) that accepts mailId and
translate and exposes a downloadAttachment(attachment: EmailAttachment) async
handler that performs the service call, blob/url creation and revoke, and error
notification (ToastType.Error) so Preview only calls the hook-provided handler;
update Preview to call the hook’s downloadAttachment and remove all download
logic from the component.
- Around line 83-88: Replace the mouse-only DownloadSimpleIcon click handler
with a real, keyboard-accessible <button type="button"> that calls
onDownload(attachment); wrap the DownloadSimpleIcon inside that button, move the
onClick to the button, preserve styling via className (e.g.,
className="cursor-pointer" or better "btn-icon"), and add an accessible label
using translate('mail.downloadAttachment') (or a similar i18n key) passed to
aria-label; then add that i18n key and translated strings to all locales (en,
es, fr, it).

In `@src/test-utils/fixtures.ts`:
- Around line 169-171: The fixture randomly sets hasAttachment and attachments
independently causing inconsistent mail states; compute attachments first (using
the existing attachments expression: faker.datatype.boolean() ?
[getMockedAttachment()] : []) and then set hasAttachment to reflect the actual
list (e.g., hasAttachment = attachments.length > 0) so tests see a consistent
state; update the mail fixture so references to attachments and hasAttachment
use those names (attachments, hasAttachment) in that order.

---

Outside diff comments:
In `@src/components/compose-message/hooks/useComposeSend.test.ts`:
- Around line 35-50: Update the renderSend test helper and add unit tests for
useComposeSend.send to verify attachment filtering and payload composition:
write tests that call renderSend (which returns result and onSent) and invoke
result.current.send for both cleartext and encrypted flows, mocking attachments
with various states and asserting only attachments with state === "done"
contribute a blobId to the final payload (and others are excluded), and assert
the constructed payload includes the expected blobId fields and that onSent is
called with that payload; reference the renderSend helper, useComposeSend hook,
and the send method on result.current to locate where to add these cases.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78593500-2e2b-4bda-8b7a-18135fe1f83a

📥 Commits

Reviewing files that changed from the base of the PR and between 41ad2c3 and dca2821.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • package.json
  • src/components/compose-message/components/AttachmentList.tsx
  • src/components/compose-message/hooks/useComposeMessage.ts
  • src/components/compose-message/hooks/useComposeSend.test.ts
  • src/components/compose-message/hooks/useComposeSend.ts
  • src/components/compose-message/index.tsx
  • src/features/mail/MailView.tsx
  • src/features/mail/components/mail-preview/index.tsx
  • src/features/mail/components/mail-preview/preview/index.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
  • src/services/sdk/index.ts
  • src/test-utils/fixtures.ts

Comment thread package.json
Comment thread src/components/compose-message/hooks/useComposeMessage.ts
Comment thread src/components/compose-message/index.tsx
Comment thread src/features/mail/components/mail-preview/preview/index.tsx
Comment thread src/features/mail/components/mail-preview/preview/index.tsx
Comment thread src/test-utils/fixtures.ts
Base automatically changed from feature/custom-hook-to-upload-attachments to master June 4, 2026 13:55
@xabg2 xabg2 merged commit def5593 into master Jun 4, 2026
4 checks passed
@xabg2 xabg2 deleted the feature/attachments branch June 4, 2026 14:01
@sonarqubecloud

sonarqubecloud Bot commented Jun 4, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
16.7% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants