---
title: "Custom file storage, MailKaro Docs"
description: "Custom file storage in MailKaro: keep uploads on your own infrastructure, no cloud secret ever reaches the browser."
---

# Custom file storage

Content & assets

# Custom file storage

Keep uploads on your own infrastructure, no cloud secret ever reaches the browser.

- **Private S3 connector**, `{ mode: "private", listEndpoint }` on your backend lists + presigns.
- **Upload handler**, `onImageUpload(file)` stores the file on your CDN and returns a URL.
- **Public bucket**, a region/bucket config for a simple public store.

See also [Amazon S3](https://mailkaro.com/docs/amazon-s3) and [image uploads](https://mailkaro.com/docs/image-uploads).

## Private bucket: the endpoint contract

With `{ mode: "private", listEndpoint }` the editor sends a GET to `listEndpoint` and shows what comes back. Return `{ items: [...] }` or a bare array. Each item needs a `url`; `key`, `name` or `label`, `thumbnailUrl`, `kind`, `alt` and `size` are optional.

json

```
{ "items": [ { "key": "campaigns/hero.png", "url": "https://assets.example.com/campaigns/hero.png?X-Amz-Signature=…", "kind": "image" } ] }
```

The request carries the user's cookies (`credentials: "include"`) and any `headers` you set, so your backend can limit the list to the signed-in organization. On a non-2xx response the editor shows the `message` field of the JSON body as the error.

## Presigned URLs expire

Presigned S3 URLs last at most 7 days. Re-resolve them at send time, or serve the objects from a public or CDN address (see [asset domain](https://mailkaro.com/docs/asset-domain)), so an email sent later does not show broken images.

## Upload handler

`onImageUpload(file, { kind })` must resolve to `{ url, alt? }`. Without it, uploads are stored in the design as base64 `data:` URLs, which make the email much larger and which many email clients strip. The File block will not upload at all without a handler.

Source: https://docs.mailkaro.com/custom-file-storage
