Car Image API

Clientes · Car Image API

Como incorporar imagens de carros em e-mails com URLs assinadas que nunca quebram

A Car Image API coloca a primeira renderização de cada cliente na caixa de entrada dele com uma URL assinada de renovação automática: o mecanismo, as contas de créditos e o código para você fazer o mesmo.

O problema: uma imagem que precisa funcionar em março

Toda conta na Car Image API recebe um breve recado do Jonathan cerca de uma hora após a primeira renderização. O mais óbvio a incluir nesse e-mail é a própria renderização: exatamente o Porsche 911 2024 ou o Dodge Charger 2006 que a pessoa acabou de pedir, como prova de que a integração funcionou e como um pequeno motivo de orgulho.

O e-mail é a pior superfície possível para uma URL assinada. Uma página é renderizada de novo a cada visita; um e-mail é renderizado uma vez e aberto quando o leitor chega a ele, um dia, um mês ou um ano depois, muitas vezes pelo proxy de imagens do Gmail. As URLs de entrega assinadas que a API já oferecia tinham TTL máximo de sete dias, o limite certo para um navegador, mas que garante uma imagem quebrada na caixa de entrada já na segunda semana. Anexar o PNG ou incorporá-lo inline deixa cada mensagem mais pesada.

O que queríamos era uma URL que se comportasse como uma imagem hospedada, continuasse funcionando enquanto alguém pudesse abrir o e-mail e, ainda assim, cobrasse da conta de forma justa pelo que realmente entrega.

A solução: URLs assinadas com renovação automática

Adicionamos uma flag ao POST /api/v1/image-urls: renew: true. Uma URL renovável é gerada e cobrada exatamente como qualquer outra: um crédito, válida por ttl_seconds. A diferença está no que acontece depois do TTL. Em vez de um 403, o primeiro carregamento em cada nova janela de ttl_seconds cobra novamente um crédito da conta emissora e entrega a imagem; todos os demais carregamentos nessa janela são gratuitos e podem ser armazenados em cache publicamente, assim como na primeira janela. Isso continua até renews_until, com prazo de até um ano.

Três propriedades surgiram desse design, e cada uma fez diferença para o e-mail:

  • Nada para reenviar ou gerar de novo. A URL no e-mail é a URL para sempre. Nenhum cron job reescrevendo links armazenados, nenhum segundo e-mail com uma imagem nova.
  • Pague por aberturas, não por tempo. Uma janela que ninguém abre não custa nada. Um e-mail lido por uma pessoa em um dia custa o único crédito para o qual foi gerado; um aberto toda semana durante um ano custa cerca de 52 créditos, cinco centavos.
  • Cobrança idempotente. Cada janela é uma linha do registro contábil com a chave renew:<url id>:<window>, única por URL e janela. Cinquenta aberturas em uma semana, ou dois proxies de e-mail disputando a primeira abertura, pagam uma vez só.

Os e-mails de ciclo de vida geram essas URLs pelo mesmo caminho de código que o endpoint público usa, como tokens de plataforma: não vinculados a nenhuma chave de API e nunca cobrados, porque são nossos, mas servidos pela mesma rota GET /api/v1/delivery/{token}, com as mesmas janelas de sete dias e o mesmo cache. A imagem no e-mail da primeira renderização é o produto demonstrando a si mesmo.

Gere uma URL renovável para a primeira renderização do cliente
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…"
}

O e-mail em si

O template é feito em React Email com estilos inline, porque os clientes de e-mail removem as folhas de estilo. A renderização fica em uma célula de tabela com borda sobre um fundo suave, solicitada com 480 pixels e exibida com 240 para continuar nítida em telas retina, com um selo acima e duas linhas de legenda abaixo. O PNG transparente é a essência do produto, então o mantemos; em um Outlook antigo que pinta o canal alfa de preto, uma renderização com format: "jpg" sobre um fundo de célula sólido é a alternativa.

O card, em HTML puro
<!-- 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>

Todo o resto da mensagem é texto: o que é a URL, por que ela continua funcionando, o POST exato para gerar uma para o próprio veículo e um link para esta página. O e-mail traz um link de cancelamento de inscrição e os cabeçalhos List-Unsubscribe, e cada link de produto é marcado para que possamos ver, no relatório de aquisição, se a história que você está lendo trouxe alguém de volta.

Faça você mesmo em três etapas

  1. Gere no servidor, ao montar o e-mail. Uma chamada por veículo, ou até cinquenta por chamada para um resumo. Armazene url e renews_until junto com a mensagem.
  2. Coloque a URL em uma <img> com largura, altura e texto alternativo. O texto alternativo nomeia o veículo ("Porsche 911 2024, vermelho, vista frontal três-quartos"), não "carro".
  3. Mantenha a conta com saldo. Uma renovação com saldo zerado responde 402 e a imagem continua quebrada até os créditos entrarem; a recarga automática no painel elimina esse modo de falha.
Com o SDK para TypeScript
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

A referência completa de parâmetros, os cabeçalhos de cache e os códigos de erro estão na documentação de URLs assinadas. A skill de agente que acompanha o plugin conhece as mesmas regras; assim, um assistente a quem se pede para "colocar este carro na newsletter" gera uma URL renovável em vez de colar uma chave em um template.

Perguntas frequentes

Por que não anexar a imagem ou incorporá-la em base64?
Anexos e base64 incham cada mensagem. Uma URL assinada tem algumas centenas de bytes e carrega pelo proxy de imagens do cliente de e-mail como qualquer imagem hospedada.
Quanto custa uma URL com renovação automática?
Um crédito quando é criada e, depois, um crédito para cada janela adicional de ttl_seconds em que ela de fato é aberta, até renews_until. Com um TTL de sete dias, isso dá no máximo um crédito por semana por imagem, e zero nas semanas em que ninguém abre o e-mail. US$ 1 compra 100 créditos.
O que acontece se a conta ficar sem créditos?
O carregamento responde 402 Insufficient credits e a imagem aparece quebrada até que o saldo seja recarregado; a URL em si continua válida e volta a funcionar no próximo carregamento. A recarga automática no painel evita que uma conta de produção chegue a esse limite.
Isso funciona no Gmail, no Outlook e no Apple Mail?
Sim. O endpoint de entrega é um GET público simples com Cache-Control, um ETag e cabeçalhos CORS, exatamente o que os proxies de imagem esperam. A transparência do PNG é exibida em todos os clientes atuais; para versões muito antigas do Outlook, solicite o formato jpg com fundo sólido.
É possível revogar uma URL?
Revogue a chave de API que a gerou e todas as URLs dessa chave respondem 403 no próximo carregamento, renovações incluídas. Caches compartilhados continuam servindo por no máximo uma hora depois disso.