---
title: "Amazon S3, MailKaro Docs"
description: "Add an “Amazon S3” tab backed by your own bucket. Two modes: public (list in the browser) or private (your backend lists + presigns)."
---

# Amazon S3

Content & assets

# Amazon S3

Add an “Amazon S3” tab backed by your own bucket. Two modes: **public** (list in the browser) or **private** (your backend lists + presigns).

## Public bucket

tsx

```
<EmailEditor
 s3={{
 region: "us-east-1", bucket: "my-assets", prefix: "email/", // optional
 publicBaseUrl: "https://cdn.example.com" // optional (CloudFront)
 }}
/>
```

The browser lists the bucket via `ListObjectsV2` and references each object's public URL. Objects must be public-read to render in a sent email. If listing needs credentials, you may pass `accessKeyId`/`secretAccessKey`, but…

⚠ **Never put a broad AWS key in the browser**, it's visible to end-users. If you must sign the list request, use a **read-only, list-only** IAM key scoped to that one bucket (`s3:ListBucket` + `s3:GetObject`). For anything sensitive, use private mode below.

## Private bucket

Your backend holds the AWS secret, lists the bucket, and returns presigned URLs. The connector just fetches your endpoint.

tsx

```
<EmailEditor
 s3={{
 mode: "private", listEndpoint: "https://app.example.com/api/s3-assets", headers: { Authorization: "Bearer …" } // optional
 }}
/>
```

### Backend contract

Return JSON in this shape (or a bare array of items):

json

```
{
 "items": [
 { "key": "photos/a.png", "url": "https://…presigned…", "name": "a.png", "kind": "image" }
 ]
}
```

Only `url` is required per item; `key`, `name`, `kind`, `thumbnailUrl`, `alt` are optional (`kind` is inferred from the path when omitted).

ℹ Presigned URLs expire (max 7 days). For reliable email, re-resolve at send time or make the objects public.

Source: https://docs.mailkaro.com/amazon-s3
