Car Image API

Kunden · Car Image API

So betten Sie Fahrzeugbilder in E-Mails ein – mit signierten URLs, die nie ins Leere laufen

Car Image API schickt jedem Kunden sein erstes Rendering mit einer sich automatisch erneuernden signierten URL ins Postfach: der Mechanismus, die Credit-Rechnung und der Code, um es selbst umzusetzen.

Das Problem: ein Bild, das auch im März noch funktionieren muss

Jedes Konto bei Car Image API erhält etwa eine Stunde nach dem ersten Rendering eine kurze Nachricht von Jonathan. Das Naheliegendste für diese E-Mail ist das Rendering selbst: genau der 2024 Porsche 911 oder 2006 Dodge Charger, den die Person gerade angefordert hat. Es beweist, dass die Integration funktioniert, und ist zugleich eine kleine Angeberei.

Für eine signierte URL ist die E-Mail der denkbar schlechteste Ort. Eine Seite wird bei jedem Besuch neu gerendert; eine E-Mail wird einmal gerendert und dann geöffnet, wann immer der Leser dazu kommt – einen Tag, einen Monat oder ein Jahr später, oft über den Bild-Proxy von Gmail. Die signierten Auslieferungs-URLs, die die API bereits anbot, hatten eine maximale TTL von sieben Tagen. Das ist die richtige Obergrenze für einen Browser, garantiert im Posteingang aber spätestens ab der zweiten Woche ein defektes Bild. Das PNG anzuhängen oder einzubetten, bläht jede Nachricht auf.

Was wir wollten, war eine URL, die sich wie ein gehostetes Bild verhält, so lange funktioniert, wie jemand die E-Mail öffnen könnte, und dem Konto trotzdem fair nur das berechnet, was tatsächlich ausgeliefert wird.

Die Lösung: sich automatisch verlängernde signierte URLs

Wir haben POST /api/v1/image-urls um ein einziges Flag ergänzt: renew: true. Eine erneuerbare URL wird genau wie jede andere erstellt und abgerechnet: ein Credit, gültig für ttl_seconds. Der Unterschied liegt darin, was nach Ablauf der TTL passiert. Statt eines 403 belastet der erste Abruf in jedem neuen Zeitfenster von ttl_seconds das ausstellende Konto erneut mit einem Credit und liefert das Bild aus; jeder weitere Abruf in diesem Fenster ist kostenlos und öffentlich cachebar, genau wie im ersten Fenster. Das geht so weiter bis renews_until, höchstens ein Jahr im Voraus.

Aus diesem Design ergaben sich drei Eigenschaften, und jede davon war für E-Mails wichtig:

  • Nichts erneut senden oder neu erzeugen. Die URL in der E-Mail bleibt für immer dieselbe. Kein Cronjob, der gespeicherte Links umschreibt, keine zweite E-Mail mit einem neuen Bild.
  • Zahlen für Öffnungen, nicht für Zeit. Ein Zeitfenster, in dem niemand die E-Mail öffnet, kostet nichts. Eine E-Mail, die eine Person an einem Tag liest, kostet den einen Credit, für den die URL erzeugt wurde; eine, die ein Jahr lang jede Woche geöffnet wird, kostet etwa 52 Credits, also fünf Cent.
  • Idempotente Abrechnung. Jedes Zeitfenster ist eine Ledger-Zeile mit dem Schlüssel renew:<url id>:<window>, eindeutig pro URL und Zeitfenster. Fünfzig Aufrufe in einer Woche oder zwei Mail-Proxys, die gleichzeitig beim ersten Öffnen zugreifen, werden nur einmal berechnet.

Die Lifecycle-E-Mails erzeugen diese URLs über denselben Codepfad wie der öffentliche Endpoint, als Plattform-Tokens: an keinen API-Schlüssel gebunden und nie abgerechnet, weil es unsere eigenen sind, aber über dieselbe Route GET /api/v1/delivery/{token} ausgeliefert, mit denselben siebentägigen Zeitfenstern und demselben Caching. Das Bild in der E-Mail zum ersten Rendering ist das Produkt, das sich selbst vorführt.

Eine verlängerbare URL für das erste Rendering des Kunden erzeugen
curl -X POST https://carimage.dev/api/v1/image-urls \
  -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images":[{"make":"porsche","model":"911","year":2024,"view":"front-3-4","color":"red","width":480}],"ttl_seconds":604800,"renew":true}'
201 Created
{
  "data": [
    {
      "id": "3f0c2a7e-1c1e-4c2a-9c39-4f1f0a4b2d10",
      "url": "https://carimage.dev/api/v1/delivery/eyJhbGciOi…",
      "expires_at": "2026-09-21T09:00:00.000Z",
      "max_uses": 0,
      "renews_until": "2027-09-14T09:00:00.000Z",
      "vehicle": {
        "make": "porsche",
        "model": "911",
        "year": 2024,
        "view": "front-3-4",
        "color": "red",
        "width": 480,
        "format": "png"
      }
    }
  ],
  "billing": {
    "charged_on": "creation",
    "credits_charged": 1,
    "credits_remaining": 4869,
    "credits_per_url": 1,
    "renewal": {
      "window_seconds": 604800,
      "credits_per_window": 1,
      "until": "2027-09-14T09:00:00.000Z"
    }
  },
  "request_id": "req_01j9x…"
}

Die E-Mail selbst

Die Vorlage nutzt React Email mit Inline-Styles, weil Mail-Clients Stylesheets entfernen. Das Rendering sitzt in einer umrandeten Tabellenzelle auf einem dezenten Hintergrund, angefordert mit 480 Pixeln und angezeigt mit 240, damit es auf Retina-Displays scharf bleibt, mit einem Badge darüber und zwei Bildunterschriftszeilen darunter. Transparentes PNG ist der Kern des Produkts, also behalten wir es bei; für ein altes Outlook, das Alpha schwarz darstellt, dient ein Rendering mit format: "jpg" auf einem einfarbigen Zellhintergrund als Fallback.

Die Karte als reines HTML
<!-- inline styles only: mail clients strip stylesheets -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
  <tr>
    <td align="center" style="padding:24px;background:#fafaf9;border:1px solid #e7e5e4;border-radius:16px;">
      <img
        src="https://carimage.dev/api/v1/delivery/eyJhbGciOi…"
        alt="2024 Porsche 911, red, front three-quarter view"
        width="240" height="240"
        style="display:block;margin:0 auto;max-width:100%;height:auto;"
      />
      <p style="margin:14px 0 2px;font:700 18px/26px -apple-system,Segoe UI,Roboto,sans-serif;color:#1c1917;">2024 Porsche 911</p>
      <p style="margin:0;font:13px/20px ui-monospace,Menlo,monospace;color:#78716c;">Front three-quarter · Red</p>
    </td>
  </tr>
</table>

Alles andere in der Nachricht ist Text: was die URL ist, warum sie weiterhin funktioniert, der genaue POST-Aufruf, um eine URL für das eigene Fahrzeug zu erstellen, und ein Link zu dieser Seite. Die E-Mail enthält einen Abmeldelink und die List-Unsubscribe-Header, und jeder Produktlink ist getaggt, damit wir im Akquisebericht sehen können, ob die Geschichte, die Sie gerade lesen, jemanden zurückgebracht hat.

In drei Schritten selbst umsetzen

  1. Erzeugen Sie sie serverseitig, wenn Sie die E-Mail erstellen. Ein Aufruf pro Fahrzeug oder bis zu fünfzig pro Aufruf für einen Digest. Speichern Sie url und renews_until zusammen mit der Nachricht.
  2. Setzen Sie die URL in ein <img> mit Breite, Höhe und Alt-Text. Der Alt-Text benennt das Fahrzeug („2024 Porsche 911, rot, Dreiviertelansicht von vorne“), nicht „Auto“.
  3. Halten Sie Ihr Konto gedeckt. Eine Verlängerung bei leerem Guthaben antwortet mit 402, und das Bild bleibt defekt, bis Credits eingehen; die automatische Aufladung im Dashboard beseitigt diese Fehlerquelle.
Mit dem TypeScript SDK
import { CarImageClient } from "@meterapp/car-image-sdk";

const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });

// One call per email: the vehicle the person rendered, at 2x the display width.
const { data } = await client.createImageUrls(
  [{ make: "porsche", model: "911", year: 2024, view: "front-3-4", color: "red", width: 480 }],
  { ttlSeconds: 604_800, renew: true, renewDays: 365 }
);
const src = data[0].url; // drop into <img src>, store data[0].renews_until next to it

Die vollständige Parameterreferenz, Caching-Header und Fehlercodes finden Sie in der Dokumentation zu signierten URLs. Der Agent-Skill, der mit dem Plugin ausgeliefert wird, kennt dieselben Regeln. Ein Assistent, der gebeten wird, „dieses Auto in den Newsletter zu setzen“, erstellt daher eine erneuerbare URL, statt einen Schlüssel in eine Vorlage einzufügen.

Häufig gestellte Fragen

Warum das Bild nicht anhängen oder als Base64 einbetten?
Anhänge und base64 blähen jede Nachricht auf. Eine signierte URL ist nur wenige hundert Bytes groß und lädt wie jedes gehostete Bild über den Bild-Proxy des Mail-Clients.
Was kostet eine sich automatisch erneuernde URL?
Ein Credit bei der Erstellung, danach ein Credit für jedes weitere Zeitfenster von ttl_seconds, in dem das Bild tatsächlich geöffnet wird, bis renews_until. Bei einer TTL von sieben Tagen sind das höchstens ein Credit pro Woche und Bild – und null für Wochen, in denen niemand die E-Mail öffnet. Für 1 $ erhalten Sie 100 Credits.
Was passiert, wenn dem Konto die Credits ausgehen?
Der Abruf antwortet mit 402 Insufficient credits, und das Bild wird als defekt angezeigt, bis das Guthaben aufgeladen ist; die URL selbst bleibt gültig und funktioniert beim nächsten Laden wieder. Mit der automatischen Aufladung im Dashboard stößt ein Produktivkonto nie an diese Grenze.
Funktioniert das in Gmail, Outlook und Apple Mail?
Ja. Der Auslieferungs-Endpoint ist ein einfacher öffentlicher GET mit Cache-Control, einem ETag und CORS-Headern – genau das, was Bild-Proxys erwarten. PNG-Transparenz wird in allen aktuellen Clients dargestellt; für sehr alte Outlook-Versionen fordern Sie format jpg mit einfarbigem Hintergrund an.
Kann eine URL widerrufen werden?
Widerrufen Sie den API-Schlüssel, der sie erzeugt hat, und jede URL dieses Schlüssels antwortet beim nächsten Laden mit 403, Verlängerungen eingeschlossen. Gemeinsame Caches liefern danach höchstens noch eine Stunde lang aus.