You're sending a receipt, a weekly digest, or a campaign with Resend, and it has an image in it — a logo, a chart, a product shot. Everything looks perfect in your local preview. Then it lands in a real inbox and the image is a gray box with a torn-corner icon. This is the single most common email-image problem, and it comes down to one fact: an email can only reliably show an image that lives at a public URL.
Why you can't just embed the image
There are three ways to get an image into an email, and two of them are traps:
- Base64
data:URI — inlining the bytes directly in thesrc. It works in your browser preview, which is why people ship it. But Gmail doesn't renderdata:images at all, and many Outlook clients don't either — support is inconsistent, so a big slice of your list sees nothing. Don't rely on it. - CID attachment — attaching the image and referencing it with
cid:. This renders more widely, but it bloats every single send with the full image payload, hurts deliverability, and Resend's HTML-first API isn't built around it. - A hosted URL —
<img src="https://…">. This is what actually works everywhere, every time. The only question is where that URL lives.
So the real task isn't "embed an image in the email." It's "host the image somewhere public, then point the email at it." Resend deliberately leaves that half to you — it's an email API, not an image host.
The two-step pattern
Host the image once to get a stable URL, then drop that URL into the HTML you hand to Resend. With PixelVault the hosting step is a single API call, and the URL you get back is permanent — which matters more for email than for almost anything else (more on that below). The upload runs server-side (it uses your secret PIXELVAULT_API_KEY, which must never ship to a browser) — the same place you call Resend from.
const form = new FormData();
form.append("file", blob, "chart.png");
const res = await fetch("https://api.pixelvault.dev/v1/images", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PIXELVAULT_API_KEY}` },
body: form,
});
// The URL is nested under `data` in the response envelope.
const { url } = (await res.json()).data;
// → https://img.pixelvault.dev/proj_abc/img_xyz.png
Do the hosting step wherever your images are born: when you generate the chart, when a designer drops a new banner into the repo, or once at build time for assets that don't change. The URL is stable, so a logo you host today keeps working in every email you ever send.
Where the image lives matters more for email
Email is a uniquely punishing place to host images, for two reasons most hosting comparisons miss.
Every open re-fetches the image. Web pages get cached in one browser. An email image gets requested by every recipient, often re-fetched through client-side proxies (Gmail serves images through googleusercontent.com), across your whole list, for as long as the message sits in the inbox. That's the exact traffic pattern that runs up a metered egress bill on S3 + CloudFront — you're billed on views, and email is nothing but views. PixelVault stores on Cloudflare R2 with zero egress fees, so a campaign that gets opened a million times costs the same to serve as one that gets opened once.
Inboxes live for years. A receipt from 2026 might be opened in 2029. If the image URL breaks — bucket cleaned up, host shut down, link expired — the email breaks retroactively, and there's nothing you can do about a message already sitting in someone's archive. PixelVault URLs are permanent and immutable (Cache-Control: public, max-age=31536000, immutable), so the picture that renders today renders in three years.
Where should the image live?
data:Sizing images for email
Email clients don't do responsive srcset well, so the convention is simple: serve a 2× image and constrain it with a plain width attribute. PixelVault resizes on the fly from transform params, so you host the original once and ask for the size you need at send time — ?w=1200 served, width="600" displayed, crisp on retina without shipping a separate export. Add &fmt=auto to let the CDN negotiate a modern format where the client supports it.
One honest caveat
Hosting your images well makes them render reliably and keeps your costs flat — it does not improve inbox placement or open rates. Deliverability is a function of your sending domain, SPF/DKIM/DMARC, and list hygiene: Resend gives you the domain-authentication tooling, but the DNS records and your sending practices are still yours to get right. Don't switch image hosts expecting to land in more inboxes. Switch because your images stop breaking and your egress bill stops scaling with your success.
Free to start
PixelVault's free tier includes 200 MB storage, 500 uploads/month, and 1 GB bandwidth — no credit card, zero egress. Plenty to host the images for a transactional flow or a modest campaign list before you pay anything. Paid plans start at $9/month.
There's a copy-paste helper for the react-email stack in the docs, or start from the API quickstart →