Misar IO Docs
MisarMailApi Reference

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

ConstraintValue
Max file size10 MB
URL validity24 hours
Storage bucketemail-attachments (scoped per user)

Allowed file types

TypeExtensions
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

StatusErrorCause
400No file providedfile field missing from form data
400File too largeFile exceeds 10 MB
400File type not allowedExtension not in the allowed list
401UnauthorizedNo active session
500Storage upload failedSupabase Storage error — retry