Car Image API

Klienci · Car Image API

Jak osadzać zdjęcia samochodów w e-mailach za pomocą podpisanych URL-i, które nigdy nie przestają działać

Car Image API dostarcza pierwszy render każdego klienta do jego skrzynki z automatycznie odnawianym podpisanym URL-em: mechanizm, obliczenia i kod, by zrobić to samodzielnie.

Problem: obraz, który musi działać w marcu

Każde konto w Car Image API otrzymuje od Jonathana krótką wiadomość mniej więcej godzinę po pierwszym renderze. Najbardziej oczywistą rzeczą do umieszczenia w tym e-mailu jest sam render: dokładnie to Porsche 911 z 2024 roku czy Dodge Charger z 2006 roku, o które dana osoba właśnie poprosiła – jako dowód, że integracja działa, i jako drobna przechwałka.

E-mail to najgorsze możliwe miejsce dla podpisanego URL. Strona jest renderowana ponownie przy każdej wizycie; e-mail jest renderowany raz i otwierany wtedy, gdy odbiorca do niego dotrze – dzień, miesiąc lub rok później, często przez proxy obrazów Gmaila. Podpisane URL-e dostarczania, które API już oferowało, miały maksymalny TTL wynoszący siedem dni – to właściwy limit dla przeglądarki, ale w skrzynce odbiorczej gwarantuje niewyświetlający się obraz już w drugim tygodniu. Dołączanie PNG jako załącznika lub osadzanie go w treści zwiększa rozmiar każdej wiadomości.

Chcieliśmy URL-a, który zachowuje się jak hostowany obraz, działa tak długo, jak ktokolwiek może otworzyć e-mail, i nadal uczciwie obciąża konto za to, co faktycznie serwuje.

Rozwiązanie: automatycznie odnawiane podpisane adresy URL

Do POST /api/v1/image-urls dodaliśmy jedną flagę: renew: true. Odnawialny URL jest generowany dokładnie tak jak każdy inny, nalicza swój obraz jako obraz pojazdu w danym miesiącu i jest ważny przez ttl_seconds. Różnica dotyczy tego, co dzieje się po upływie TTL. Zamiast błędu 403 pierwsze wczytanie w każdym nowym oknie ttl_seconds odnawia URL i serwuje obraz; odnowienie nalicza obraz raz w miesiącu, w którym nastąpi, i nic więcej, jeśli obraz został już naliczony w tym miesiącu. Każde kolejne wczytanie w tym oknie może być publicznie buforowane, tak samo jak w pierwszym oknie. Trwa to do renews_until, maksymalnie rok naprzód.

Z tego projektu wynikły trzy właściwości i każda z nich miała znaczenie dla e-maili:

  • Nic do ponownego wysyłania ani generowania. URL w e-mailu pozostaje ten sam na zawsze. Żadnego zadania cron przepisującego zapisane linki, żadnego drugiego e-maila z nowym obrazem.
  • Liczą się otwarcia, nie czas. Okno, którego nikt nie otworzy, niczego nie nalicza. E-mail przeczytany przez jedną osobę jednego dnia to jeden obraz pojazdu, dla którego został wygenerowany; e-mail otwierany co tydzień przez rok nalicza ten obraz raz w każdym miesiącu, w którym zostanie otwarty — najwyżej dwanaście razy.
  • Idempotentne zliczanie. Każde okno jest zapisywane raz, z kluczem renew:<url id>:<window>, unikalnym dla URL-a i okna. Pięćdziesiąt otwarć w tygodniu albo dwa proxy pocztowe ścigające się przy pierwszym otwarciu liczą się jako jedno.

E-maile cyklu życia generują te adresy URL tą samą ścieżką kodu, z której korzysta publiczny endpoint, jako tokeny platformy: niepowiązane z żadnym kluczem API i nigdy nierozliczane, bo są nasze, ale obsługiwane przez identyczną trasę GET /api/v1/delivery/{token}, z identycznymi siedmiodniowymi oknami i identycznym buforowaniem. Obraz w e-mailu o pierwszym renderze to produkt prezentujący sam siebie.

Jeden odnawialny URL dla pierwszego renderu klienta
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…"
}

Sam e-mail

Szablon powstał w React Email ze stylami inline, ponieważ klienty poczty usuwają arkusze stylów. Render znajduje się w obramowanej komórce tabeli na delikatnym tle; jest pobierany w rozmiarze 480 px i wyświetlany w rozmiarze 240 px, dzięki czemu pozostaje ostry na ekranach retina. Nad nim znajduje się etykieta w kształcie pigułki, a pod nim dwie linie podpisu. Przezroczysty PNG to sedno produktu, więc go zachowujemy; w starym Outlooku, który wypełnia kanał alfa na czarno, rozwiązaniem awaryjnym jest render format: "jpg" na jednolitym tle komórki.

Karta jako zwykły 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>

Cała reszta wiadomości to tekst: czym jest ten URL, dlaczego wciąż działa, dokładne żądanie POST pozwalające wygenerować taki URL dla własnego pojazdu oraz link do tej strony. E-mail zawiera link do wypisania się i nagłówki List-Unsubscribe, a każdy link do produktu jest oznaczony, dzięki czemu w raporcie pozyskania widzimy, czy historia, którą Państwo czytają, sprowadziła kogoś z powrotem.

Samodzielna konfiguracja w trzech krokach

  1. Generowanie po stronie serwera podczas tworzenia e-maila. Jedno wywołanie na pojazd lub do pięćdziesięciu pojazdów w jednym wywołaniu w przypadku zestawienia. Należy zapisać url i renews_until razem z wiadomością.
  2. URL należy umieścić w <img> z szerokością, wysokością i tekstem alternatywnym. Tekst alternatywny nazywa pojazd („2024 Porsche 911, czerwony, widok z przodu w trzech czwartych”), a nie „samochód”.
  3. Konto musi mieć dostępny limit. Odnowienie liczy obraz raz w miesiącu, w którym nastąpi; jeśli obrazy pojazdów z planu zostały wykorzystane, zwraca 402 do czasu odnowienia limitu 1. dnia miesiąca, dlatego plan należy dobrać do liczby wyświetlanych obrazów.
Z użyciem 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

Pełny opis parametrów, nagłówki cache i kody błędów znajdują się w dokumentacji podpisanych URL-i. Umiejętność agenta (skill) dołączona do wtyczki zna te same zasady, więc asystent poproszony o „wstaw ten samochód do newslettera” generuje odnawialny URL zamiast wklejać klucz do szablonu.

Najczęściej zadawane pytania

Dlaczego nie dołączyć obrazu jako załącznika ani nie osadzić go jako base64?
Załączniki i base64 rozdmuchują każdą wiadomość. Podpisany URL zajmuje kilkaset bajtów i wczytuje się przez proxy obrazów klienta poczty jak każdy hostowany obraz.
Ile kosztuje automatycznie odnawiany URL?
Jeden obraz pojazdu w miesiącu utworzenia, a następnie ten sam obraz liczony raz w każdym kolejnym miesiącu, w którym faktycznie nastąpi otwarcie w oknie odnowienia, aż do renews_until; miesiąc, w którym nikt nie otworzy e-maila, nie jest liczony. Plan Pro za 49 USD miesięcznie obejmuje limit 2500 obrazów pojazdów miesięcznie.
Co się dzieje, gdy miesięczna pula obrazów pojazdów się wyczerpie?
Żądanie zwraca 402, a obraz wyświetla się jako uszkodzony, dopóki miesięczny limit się nie odnowi lub plan nie zostanie podwyższony; sam URL pozostaje ważny i znów działa przy następnym wczytaniu. Obraz już policzony w tym miesiącu jest nadal serwowany.
Czy to działa w Gmailu, Outlooku i Apple Mail?
Tak. Endpoint dostarczania to zwykły publiczny GET z nagłówkami Cache-Control, ETag i CORS, czyli dokładnie tym, czego oczekują proxy obrazów. Przezroczystość PNG jest poprawnie wyświetlana we wszystkich obecnych klientach; w przypadku bardzo starych wersji Outlooka należy zażądać formatu jpg na jednolitym tle.
Czy adres URL można unieważnić?
Po unieważnieniu klucza API, który go wygenerował, każdy URL z tego klucza zwraca 403 przy następnym wczytaniu, łącznie z odnowieniami. Współdzielone pamięci podręczne serwują obraz jeszcze najwyżej przez godzinę.