File attachments
Hydra lets your team and your customers exchange files inside conversations. Attachments work across the three places conversations live:
- The inbox composer (your team replying to a customer)
- The email channel (inbound + outbound for any email-routed conversation)
- The widget (the customer attaching a file from their browser)
Every attachment is stored privately and rendered through short-lived signed URLs. Nothing is publicly readable.
Sending an attachment from the inbox
In any conversation thread:
- Click the paperclip to the left of the reply textarea — or drag-and-drop files directly onto the composer. A purple "Drop files to attach" overlay appears while you're holding files over the area.
- Files start uploading immediately. Each shows up as a chip below the textarea with one of three states:
- Uploading (purple, with an hourglass) — still in progress.
- Ready (white, with a paperclip icon) — uploaded and ready to send.
- Failed (red, with a warning) — hover the truncated error for the full reason.
- You can attach multiple files. Click ✕ on any chip to remove it before sending.
- Send is disabled while any chip is still uploading. You can send with attachments only and an empty message body.
In the rendered thread, image attachments display inline at up to 320×240 pixels. Click an image to open the lightbox. Other file types show as a download chip with the filename and size — click to open in a new tab.
Email channel — outbound and inbound
Outbound: when you reply to an email-channel conversation with attachments, those files are sent as standard MIME attachments via Resend. The customer receives them in the same email as your reply.
Inbound: when a customer replies to your email and includes attachments, they're stored alongside the inbound message and shown inline in the inbox thread, exactly like inbox-composed attachments.
There's a 3 MB per-attachment cap on inbound email. Files above that ceiling are dropped (the rest of the email still posts). This cap is a platform limit on the inbound webhook payload size — outbound and inbox-uploaded attachments are not affected and follow the regular 25 MB cap.
Widget — customers attaching files
The widget composer shows a paperclip next to the message input. The end user can attach one file per message (MVP — multi-file support will land if customers ask for it). Attachments display as a chip with a remove ✕ before send, and render inside the user's chat bubble after send.
When an attachment arrives, your team sees it in the inbox the same way as any other message attachment — inline image preview or a download chip.
Limits and behaviors
- Maximum file size: 25 MB per file (inbox + widget).
- Inbound email cap: 3 MB per attachment (see above).
- Privacy: the storage bucket is private; reads use signed URLs that expire after 1 hour. Refreshing the page mints fresh URLs.
- Tenant isolation: every attachment is stored privately, scoped to your tenant and the originating conversation, so cross-tenant access is impossible even if a signed URL leaked.
- Filename sanitization: non-alphanumeric characters in the filename are replaced with underscores when stored. The original name is preserved in the chip and download link.
Troubleshooting
"File too large" before upload. The browser-side cap caught it before any upload work happened. Compress the file or split it.
"Upload could not start". The /api/uploads endpoint rejected the request. Common causes: the conversation belongs to a different tenant; the file size exceeds 25 MB; the user session expired. Re-load the inbox or widget.
Image renders as a download chip instead of inline. The signed URL probably expired (TTL 1 hour). Refresh the conversation and the URLs re-mint.
Inbound email attachment is missing. Check whether the file was over 3 MB; the worker drops larger ones. We log a warning every time this happens — check the Cloudflare worker tail logs for dropping oversize attachment.
