Email Hero Specs: 600px, JPEG, Send-Time Renders

Published October 12, 2026 by Balaji

Uncategorized

Email-client constraints: design at 1200×630, display at 600px, JPEG under 150KB, alt text required. The use-case page is /use-cases/email-hero-images. Measure lift in your own ESP; do not copy someone else’s ROI number.

Overview

Your email hero image is the first pixel a subscriber sees — and for most campaigns, it’s also the most wasted one. It’s the same generic banner sent to 50,000 people, while every other part of the email (subject line, product recommendations, even the sender name) gets personalized.

That’s backwards. Personalized heroes are worth it only if you can measure lift in your own ESP. Generating one unique image per subscriber is a batch of signed URLs or send-time JPEGs — not a Chromium farm.

It doesn’t have to be. With a JSON-driven image API like jsontoimg, you can generate a fully personalized hero image for every subscriber in a batch — name, product, price, location, countdown timer, anything in your data — in a single API call. This guide walks through the whole pipeline: template design, batch generation, caching, and email-client-safe sizing.

Why the Hero Image Is the Highest-Leverage Pixel in Email

Before diving into code, it’s worth understanding why this matters. The data is unambiguous:

  • Emails with personalized content see 29% higher open rates and 41% higher click-through rates than static sends (The Loop Marketing, 2026)
  • Personalized emails deliver 6x higher transaction rates (Campaign Monitor)
  • Usage of AI for generating email imagery grew 340% from 2024 to 2025 (Leadoom)

The hero image sits above the fold, occupies the largest visual area, and sets expectations for everything below it. If it’s generic, the reader’s brain classifies the email as bulk before reading a single word. If it’s clearly built for *them*, the opposite happens.

The Old Ways of Doing This (and Why They Break)

Manual design in Figma/Canva

Fine for one campaign. Impossible at 10,000 subscribers. Nobody is hand-designing 10,000 unique heroes.

Dynamic image platforms (NiftyImages, Liveclicker, etc.)

These work, but they’re add-ons to your email platform, they cost per render, and you’re locked into their template engine. If you’re already running an API-driven stack, bolting on a separate visual platform is friction you don’t need.

DIY rendering with Puppeteer

Full control, but now you’re maintaining a headless browser fleet, font installation, scaling, and rendering infrastructure — all to generate one image per subscriber. That’s a lot of moving parts for a marketing email.

The jsontoimg approach

Design a template once, send JSON per subscriber, get images back. No browser infrastructure, no template lock-in, no manual design. One POST request per subscriber (or one batch request for thousands), and you’re done.

Step 1: Design the Hero Template

A hero template is a layout with placeholders. In jsontoimg you define:

  • Dimensions — 1200×630 (displayed at 600px, exported at 2x for retina)
  • Text layers — greeting, name, offer, product title, price
  • Image layers — product photo, logo, background
  • Styles — fonts, colors, alignment, text wrapping

Think of it as a design file, but expressed as JSON:

{
  "template": {
    "width": 1200,
    "height": 630,
    "background": {
      "type": "color",
      "value": "#0F172A"
    },
    "layers": [
      {
        "type": "image",
        "src": "named layer product_image",
        "x": 60,
        "y": 120,
        "width": 400,
        "height": 400,
        "fit": "cover"
      },
      {
        "type": "text",
        "value": "Hey named layer first_name,",
        "x": 520,
        "y": 140,
        "font_size": 56,
        "font_weight": 700,
        "color": "#FFFFFF",
        "align": "left"
      },
      {
        "type": "text",
        "value": "named layer product_name is back in stock",
        "x": 520,
        "y": 220,
        "font_size": 36,
        "font_weight": 400,
        "color": "#94A3B8",
        "max_width": 600,
        "wrap": true
      },
      {
        "type": "text",
        "value": "named layer price · Buy now →",
        "x": 520,
        "y": 320,
        "font_size": 44,
        "font_weight": 700,
        "color": "#F59E0B"
      }
    ]
  }
}

Design once. The template lives in your codebase (or jsontoimg’s template storage), and every subscriber’s image is a data variation of it.

Step 2: Generate One Image Per Subscriber

Now the fun part. For a list of 10,000 subscribers, you build an array of payloads — one per person — and send it in a single batch request:

import requests

API_URL = "https://app.jsontoimg.com/api/v1/render"

subscribers = [
    {
        "first_name": "Priya",
        "product_name": "Noise-Cancelling Headphones",
        "product_image": "https://cdn.example.com/products/headphones.png",
        "price": "$199"
    },
    {
        "first_name": "Marcus",
        "product_name": "Mechanical Keyboard",
        "product_image": "https://cdn.example.com/products/keyboard.png",
        "price": "$149"
    },
    # ... 9,998 more
]

# Batch: send an array, get an array back
resp = requests.post("https://app.jsontoimg.com/api/v1/renders/batch", json={
    "jobs": [
        {
            "template": "hero_v2",
            "format": "jpg",
            "layers": {
                "first_name": { "text": row["first_name"] },
                "product_name": { "text": row["product_name"] },
                "price": { "text": row["price"] },
            },
        }
        for row in subscribers[:50]
    ]
})
batch = resp.json()  # accepted jobs; poll GET /api/v1/renders/batches/{id}

Each item resolves the named layer placeholders in the template and renders a unique image. For very large lists, or when you don’t want to block on the response, switch to webhook delivery — jsontoimg calls your callback URL when the batch finishes:

resp = requests.post("https://app.jsontoimg.com/api/v1/renders/batch", json={
    "jobs": [
        {
            "template": "hero_v2",
            "format": "jpg",
            "webhookUrl": "https://api.yourdomain.com/webhooks/jsontoimg",
            "layers": {
                "first_name": { "text": row["first_name"] },
                "product_name": { "text": row["product_name"] },
                "price": { "text": row["price"] },
            },
        }
        for row in subscribers[:50]
    ]
})
# 202 + batch id. Loop remaining subscribers in chunks of 50.

Step 3: Cache and Serve the Images

Here’s the subtlety most people miss: email clients download images at open time, not send time. So you have two options:

Option A: Render at send time, host statically

Generate all images before the campaign, upload to your CDN/S3, and reference the static URLs in the email. This is the simplest and most predictable path. For a 50K list at ~100KB per image, that’s about 5GB of storage for one campaign — trivial on any object store.

Option B: Render at open time (truly dynamic)

If you want real-time personalization (countdown timers, current inventory, weather-based content), the image URL points to your API and renders on first open:

https://app.jsontoimg.com/api/v1/img/<template>?sig=...

The email client fetches this URL when the subscriber opens the message, so the image is always current. Add a CDN in front and cache by the full query string — the first open pays the render cost, the next 9,999 hit the cache.

> Practical rule: use send-time rendering for stable content (names, products, prices) and open-time rendering for anything that must stay fresh (countdowns, stock levels, live offers).

Step 4: Make the Images Email-Client-Safe

Email is the most conservative rendering environment on the web, so follow these specs when generating heroes:

Aspect Best practice
Display width 600 px (max-width: 600px, width: 100%)
Source width 1200 px (2x for retina)
File size ≤ 100–150 KB per image
Format JPEG for photos, PNG for transparent/logos
Alt text Always set — images are blocked by default in many clients
Text/image ratio Keep at least 60% text / 40% image for deliverability

In your jsontoimg template, that means designing at 1200×630 and letting the API output a compressed JPEG. A 1200px-wide hero displayed at 600px renders crisply on iPhone Retina, MacBook Pro XDR, and 4K displays without bloating the message.

And always include alt text in the email HTML — most email clients block images by default, and the alt text is what sells the click before the image loads:

<img src="https://cdn.yourdomain.com/heroes/priya-20260813.jpg"
     width="600" style="width:100%;max-width:600px;height:auto;"
     alt="Priya, your Noise-Cancelling Headphones are back in stock — $199">

The format advice above is worth checking against data rather than taking on faith. Can I email tracks client support feature by feature: WebP in email and AVIF in email are both well short of the near-universal support they enjoy in browsers, which is the whole reason a hero image ships as JPG while the same template serves WebP on the web. The other constraint is proxying: Gmail fetches and caches images through its own image proxy, so an open-time render is fetched by Google rather than by the reader, and per-open personalisation behaves differently than you would expect. Keep the markup boring too — MDN’s img reference is a reminder that width, height, and alt are doing real work when images are blocked by default. Caching is the last piece: a hero rendered at send time should go out with a long Cache-Control lifetime, because that file will never change once the message is sitting in someone’s inbox.

Real-World Patterns That Work

Birthday offers

Pull the subscriber’s birth date, generate a hero with their name and a “Happy Birthday” offer. Automated birthday emails average a 43% open rate and convert 5x better than promotional emails.

Back-in-stock alerts

The hero shows the exact product with the subscriber’s name — not a generic “Your item is available” banner. This is the HostelWorld pattern: the hero calls out something specific to the reader.

Abandoned cart

Render the actual product, price, and a countdown (“Your 15% discount expires in 6 hours”) directly into the hero. The image is the reminder; the subject line just gets them to open.

Location-based content

Show the nearest store or a local event using the subscriber’s geodata. One template, thousands of local variations.

Key Takeaways

  • Personalization is measurable: use your ESP’s lift, not a blog statistic. The hero is still the largest pixel in the message
  • One template, many images: design the hero once in JSON, then generate one image per subscriber with a single batch API call
  • Send-time vs open-time: render stable content (name, product, price) at send time; use open-time rendering for countdowns and live data
  • Mind the email specs: 600px display / 1200px source, ≤150KB, JPEG, alt text always, 60/40 text-to-image ratio
  • No infrastructure: no Puppeteer fleet, no design tooling, no per-render lock-in — just JSON in, images out

FAQ

What size should a personalized email hero image be?

Design for 600 px of display width — the safe content width in most email clients — and render at 2x so it stays sharp on retina screens. The client scales it down with CSS.

Should I render email hero images at send time or at open time?

Send time is cheaper and safer: you render once per subscriber, host the file, and the email just links to it. Open time is only worth the latency when the content genuinely has to be current at the moment of opening.

Which image format is safest for email?

JPG. Email client support for WebP and AVIF is still uneven, so keep those for the web and send JPG (or PNG when you need transparency) in the message itself.

How many credits does a personalized campaign cost?

One credit per image, so one per subscriber if every hero is unique. A 10,000-subscriber send is 10,000 images; re-sends that reuse the same stored file cost nothing more.

Related Resources

Zoom out: How JSON-to-Image Conversion Works maps the whole template, named-layer, and render-job architecture.