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.
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}'{
"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…"
}ttl_seconds: 604800 (siedem dni, maksimum) oznacza najwyżej jedno odnowienie tygodniowo na obraz, a obraz jest naliczany najwyżej raz w miesiącu, niezależnie od liczby odnowień. Jednogodzinny TTL z renew: true też działa, ale w aktywnej skrzynce odbiorczej URL byłby odnawiany co każdą godzinę, w której e-mail jest otwierany.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.
<!-- 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
- 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ć
urlirenews_untilrazem z wiadomością. - 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”. - 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.
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 itPeł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ę.