Misar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisar PlatformMisar IdentityMisar Posts API
Api 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/mail/api/attachments/upload

Submit the file as a multipart/form-data upload using the field name file. Requires an active session.

Request body (multipart/form-data)

filefilebodyrequired

The file to upload. Max 10 MB. Allowed: documents (.pdf, .docx, .xlsx, .txt, .csv), images (.png, .jpg, .gif), archives (.zip).

Response fields

successboolean

true when the upload succeeded.

urlstring

Signed URL valid for 24 hours.

filenamestring

Original file name.

size_bytesnumber

File size in bytes.

content_typestring

Detected MIME type.

storage_pathstring

Per-user storage path. Store this long-term and request a fresh signed URL when needed.

curl -X POST https://api.misar.io/mail/api/attachments/upload \
  -F "file=@/path/to/invoice-june-2025.pdf"
{
  "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"
}

Limits

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

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 https://api.misar.io/mail/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