PixelVault Email Images
Take an email template that references local images and make it send-ready: upload each local image to PixelVault and rewrite the reference to the permanent CDN URL it returns.
Why this is needed
An email can only reliably render an image that lives at a public URL:
- Base64
data:images don't render in Gmail, and inconsistently in Outlook — recipients see a broken box. - CID attachments bloat every send and hurt deliverability.
- A hosted
https://URL renders everywhere. That's the target.
Resend (and every other email API) sends the message but doesn't host images, so the images have to live somewhere public first. PixelVault URLs are permanent and immutable on zero-egress storage — they render for the life of the inbox and cost the same whether the email is opened once or a million times.
Prerequisites
The pixelvault CLI must be installed and configured. If the command is not
found, install it:
npm install -g pixelvault-cli
If no API key is configured, tell the user to run /pixelvault-setup first or set
PIXELVAULT_API_KEY in their environment.
Steps
Check CLI + auth — Run
which pixelvault, thenpixelvault whoami --json. If either fails, stop and tell the user to run/pixelvault-setupor setPIXELVAULT_API_KEY.Find image references in the template(s) at
$ARGUMENTS. Look for:- HTML:
<img src="..."> - react-email / JSX:
<Img src="..." />— only whensrcis a string literal (src="./logo.png"orsrc={"./logo.png"}) - CSS/inline:
url(...)instyle/background - Inline base64
data:image/...URIs
Classify each reference and handle only the ones you can safely resolve:
- Local file path (relative or absolute path that exists on disk) → host it. Resolve relative paths against the template file's directory.
- Inline base64
data:URI → host it via the decode step below. - Non-literal
src={expr}— a variable, prop, or expression such assrc={logo}orsrc={props.image}→ do NOT rewrite it. There's no literal path to resolve, and editing it would corrupt the template. Report it instead so the user can host that asset and pass the URL in themselves. - Already
http(s)://, acid:reference, or a 1×1 tracking pixel → skip.
- HTML:
Deduplicate — the same asset (a logo) is often referenced many times. Upload each unique file once and reuse the URL.
Upload each unique image:
# Local file on disk pixelvault upload <resolved-path> # → https://img.pixelvault.dev/proj_abc/img_xyz.pngFor an inline base64
data:URI, decode the payload to a temp file first, then upload that file (pick the extension from the URI's mime type):# data:image/png;base64,AAAA... → temp file → upload printf '%s' "<base64-payload>" | base64 -d > /tmp/pv-email-1.png pixelvault upload /tmp/pv-email-1.pngThe CLI prints one CDN URL per line to stdout.
Rewrite the references you hosted, replacing each local path or base64
data:URI with its hosted URL. Leave the non-literalsrc={expr}references untouched — you only reported those. When the image has a known displaywidth, append?w=<2×width>&fmt=autofor retina and keep thewidthattribute — e.g.width="120"becomessrc="https://img.pixelvault.dev/…?w=240&fmt=auto".Report the mapping (source → hosted URL), which files you edited, and any non-literal
src={expr}references you left for the user to handle.
Output Contract
pixelvault uploadprints only URLs to stdout (one per line); human messages go to stderr. Use--jsonif you need the id/size/filename.- Edit the template files in place and summarize every change — never rewrite a reference silently.
Error Handling
| Error | Action |
|---|---|
command not found: pixelvault |
Tell user to npm install -g pixelvault-cli |
No API key configured |
Tell user to run /pixelvault-setup or set PIXELVAULT_API_KEY |
401 Unauthorized |
API key invalid — tell user to run pixelvault login |
413 Payload Too Large |
Image exceeds plan limit (5 MB free, 50 MB paid) |
415 Unsupported Media Type |
File is not a supported image format |
| Local path not found | Report which reference couldn't be resolved; don't rewrite it |
Example
Given emails/receipt.tsx:
import { Img } from "@react-email/components";
export function Receipt() {
return <Img src="./assets/logo.png" width="120" alt="Acme" />;
}
After running the skill:
import { Img } from "@react-email/components";
export function Receipt() {
return <Img src="https://img.pixelvault.dev/proj_abc/img_xyz.png?w=240&fmt=auto" width="120" alt="Acme" />;
}
Now the template is safe to render and hand to Resend:
import { Resend } from "resend";
import { render } from "@react-email/render";
import { Receipt } from "./emails/receipt";
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: "receipts@yourdomain.com",
to: "customer@example.com",
subject: "Your receipt",
html: await render(<Receipt />),
});
Note on deliverability
Hosting images well makes them render reliably and keeps egress flat — it does not affect inbox placement. Deliverability is a function of the sending domain and SPF/DKIM/DMARC — the email provider gives you the authentication tooling, but the DNS records and list hygiene are yours. Don't promise better open rates; promise images that don't break.
Related
/pixelvault-upload— host individual images without the template rewrite./pixelvault-transform— the full transform vocabulary for the hosted URLs (?w=,?fmt=,?segment=, watermarks).