Attachments
Upload email attachments to Supabase Storage and reference them in outbound emails
Upload files to MisarMail's attachment storage and get back a URL to reference in your emails. Files are stored in a per-user Supabase Storage bucket and served via time-limited signed URLs.
Requires active session (dashboard only).
Upload an attachment
POST /api/attachments/upload
Content-Type: multipart/form-data
Submit the file as a multipart/form-data upload using the field name file.
Limits
| Constraint | Value |
|---|---|
| Max file size | 10 MB |
| URL validity | 24 hours |
| Storage bucket | email-attachments (scoped per user) |
Allowed file types
| Type | Extensions |
|---|---|
| Documents | .pdf, .docx, .xlsx, .txt, .csv |
| Images | .png, .jpg, .gif |
| Archives | .zip |
Response
{
"success": true,
"url": "https://supabase-io.misar.io/storage/v1/object/sign/email-attachments/usr_abc123/invoice-june-2025.pdf?token=eyJ...",
"filename": "invoice-june-2025.pdf",
"size_bytes": 84321,
"content_type": "application/pdf",
"storage_path": "usr_abc123/invoice-june-2025.pdf"
}url is a signed URL valid for 24 hours. If you need to reference the attachment in an email sent later, re-upload the file or request a refreshed URL before sending.
Signed URLs expire after 24 hours. Do not store the URL long-term — store the storage_path and generate a fresh signed URL when needed.
Using the URL in an email
After uploading, reference the URL in your email HTML as a download link or inline image.
As a download link
<a href="{{attachment_url}}">Download your invoice (PDF)</a>
Or hardcoded in a one-off send:
<a href="https://supabase-io.misar.io/storage/v1/object/sign/...">Download invoice</a>
As an inline image
For inline images in HTML emails, embed the signed URL directly in an <img> tag:
<img src="https://supabase-io.misar.io/storage/v1/object/sign/..." alt="Product diagram" width="600" />
For brand logos and reused images, use a CDN-hosted URL directly in your template HTML instead of uploading to attachment storage each time.
Example
curl -X POST /api/attachments/upload \
-F "file=@/path/to/invoice-june-2025.pdf"async function uploadAttachment(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/attachments/upload', {
method: 'POST',
credentials: 'include',
body: formData,
// Do NOT set Content-Type — the browser sets it with the boundary automatically
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error ?? 'Upload failed');
}
return res.json() as Promise<{
success: boolean;
url: string;
filename: string;
size_bytes: number;
content_type: string;
storage_path: string;
}>;
}import { readFileSync } from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';
const form = new FormData();
form.append('file', readFileSync('./invoice-june-2025.pdf'), {
filename: 'invoice-june-2025.pdf',
contentType: 'application/pdf',
});
const res = await fetch('https://api.misar.io/mail/attachments/upload', {
method: 'POST',
headers: { Cookie: `session=${SESSION_TOKEN}`, ...form.getHeaders() },
body: form,
});
const { url } = await res.json();
console.log('Attachment URL:', url);Error responses
| Status | Error | Cause |
|---|---|---|
400 | No file provided | file field missing from form data |
400 | File too large | File exceeds 10 MB |
400 | File type not allowed | Extension not in the allowed list |
401 | Unauthorized | No active session |
500 | Storage upload failed | Supabase Storage error — retry |