Email clients can't load images from a git repository — they need a public URL. The **asset library** closes that gap: commit an image alongside your templates, reference it by its repository path, and SendOps uploads it to your [Edge CDN](/edge/edge-stack) and rewrites the reference to a public CloudFront URL when the template deploys to SES.

Image provenance is **git** — images live in your connected repository, exactly like your templates. The library in SendOps is a **read-only** view of what has been promoted; you don't upload images through the dashboard. You can also read the library **through the SendOps API** — see the [Assets API reference](https://developers.sendops.dev/api-reference/assets).


  Images are promoted only when your [Edge stack](/edge/edge-stack) is active — that's where the S3 bucket and CloudFront distribution that host them live. Without an active Edge stack, image references are left untouched.


## Referencing an image in a template

You can reference a repository image two ways. Both are rewritten to the CDN URL at deploy time:

- **The `asset` helper** — `{{asset "static/logo.png"}}`
- **A repository-relative `src`** — ``

The path is relative to your repository (or to your connection's path prefix, if you use one). Leading `./` or `/` are accepted — `static/logo.png`, `./static/logo.png`, and `/static/logo.png` all match the same image.

```html
<!-- Both of these resolve to the same CDN URL on deploy -->


```


  Anything that is already a full URL (`https://…`, `//…`), a `data:`/`cid:`/`mailto:` URI, or still contains an unresolved Handlebars expression (`{{…}}`) is left exactly as written. Only repository-relative references to known images are rewritten.


## How images are discovered

Which images get promoted depends on whether your repository has a [`sendops.json` manifest](/templates/manifest-file):

- **With a manifest** — only the images listed under the manifest's `images` key are promoted. An image in your repo that isn't listed will **not** be uploaded, even if a template references it.
- **Without a manifest** — SendOps scans your connection's path prefix and promotes every supported image it finds.


  If your repo has a `sendops.json`, remember to add each image to the `images` array — otherwise it won't be promoted. See [Manifest File](/templates/manifest-file#images) for the syntax.


### Supported image types

`.png`, `.jpg` / `.jpeg`, `.gif`, `.webp`, `.svg`, `.ico`, and `.avif`.


  Images larger than 1&nbsp;MB are currently skipped during promotion. Keep template images well under that — smaller images also load faster in the inbox.


## When promotion happens

Image promotion runs as part of a **template sync** — the same job that runs when you push to your connected branch or trigger a manual sync. For each discovered image SendOps:


  <Step title="Fetches and fingerprints the image">
    The image bytes are hashed (SHA-256). The hash becomes the object's address, so identical content is only ever stored once.
  </Step>
  <Step title="Uploads it to your Edge bucket">
    The image is stored at a content-addressed key — `{org}/assets/{hash}{ext}` — in your Edge S3 bucket. If the image hasn't changed since the last sync, the upload is skipped.
  </Step>
  <Step title="Rewrites references on deploy">
    When templates deploy to SES, every `{{asset "…"}}` token and repository-relative `src` that matches a promoted image is rewritten to its public CDN URL (`https://<your-assets-domain>/{org}/assets/{hash}{ext}`).
  </Step>



  Because images are content-addressed and may be referenced by emails you've already sent, the underlying S3 objects are retained. Removing an image from your repo removes it from the library view, but the stored object stays so old emails keep rendering.


## The asset library page

The **Asset Library & Images** page in the **Edge / CDN** area shows every image that has been promoted on your default branch.



A status bar at the top shows your **Edge stack status** and the **domain images are served from** — your [branded image domain](/edge/custom-image-domain) if you've set one up, otherwise the default CloudFront domain — with a link to the Edge & CDN settings.

Each image card shows:

- A **thumbnail** and its **source path** (e.g. `static/logo.png`).
- Its **file size**.
- Which **templates reference it** — each is a link to that template — or an *unused* label when nothing does.
- When it was **last synced**.

A header badge summarises usage — the number of images and total storage used (and, if your plan has an image-storage limit, a warning as you approach it). You can **search** the grid by source path and **filter** to **Unused** images (those no template references).


  An unused image hasn't been deleted — its content-addressed file stays in your Edge bucket so older emails keep rendering. The filter just helps you spot images you may want to remove from the repository.


The page is read-only. To add, change, or remove an image, commit the change to your repository and let it sync.

### Image detail

Click any image to open its detail page — a large preview plus its metadata: **content type**, **size**, **pixel dimensions** (when SendOps can read them — SVGs and some formats don't report a size), the **content hash** and **CDN URL** (both copyable), the **branch** it was promoted on and any other branches the same bytes appear on, and a **Referenced by** card linking every template that uses the image.



## Example layout

A typical repository keeps images in a `static/` (or `images/`) folder next to the templates that use them:


```
repo/
├── sendops.json
├── templates/
│   └── welcome.html        ← references {{asset "static/logo.png"}}
└── static/
    └── logo.png            ← promoted to your Edge CDN
```


If `sendops.json` is present, list the image so it gets promoted:

```json
{
  "templates": {
    "welcome": { "path": "templates/welcome.html" }
  },
  "images": [
    "static/logo.png"
  ]
}
```

## What's next?

- [Edge Stack](/edge/edge-stack) — enable the CDN that hosts your images.
- [Manifest File](/templates/manifest-file#images) — declare images in `sendops.json`.
- [Custom Image Domain](/edge/custom-image-domain) — serve these images from your own branded subdomain.
- [Assets API](https://developers.sendops.dev/api-reference/assets) — read the asset library programmatically.