문제: 3월에도 제대로 표시되어야 하는 이미지
Car Image API의 모든 계정은 첫 렌더 후 약 한 시간 뒤에 Jonathan이 보내는 짧은 메일을 받습니다. 이 이메일에 넣을 가장 자연스러운 내용은 렌더 그 자체입니다. 방금 요청한 바로 그 2024 Porsche 911이나 2006 Dodge Charger를, 연동이 잘 되었다는 증거이자 작은 자랑으로 담는 것입니다.
이메일은 서명된 URL에 가장 불리한 환경입니다. 웹 페이지는 방문할 때마다 다시 렌더링되지만, 이메일은 한 번 렌더링된 뒤 수신자가 읽을 때, 즉 하루, 한 달, 또는 1년 뒤에 열리며 Gmail 이미지 프록시를 거치는 경우도 많습니다. API가 기존에 제공하던 서명된 전송 URL은 최대 TTL이 7일이었는데, 브라우저에는 적절한 상한이지만 받은편지함에서는 2주 차쯤이면 이미지가 반드시 깨집니다. PNG를 첨부하거나 인라인으로 넣으면 모든 메시지가 무거워집니다.
저희가 원한 것은 호스팅된 이미지처럼 작동하고, 누군가 이메일을 열어볼 수 있는 기간 내내 계속 작동하며, 실제로 제공한 만큼만 계정에 공정하게 과금하는 URL이었습니다.
해결책: 자동 갱신되는 서명된 URL
POST /api/v1/image-urls에 플래그 하나를 추가했습니다: renew: true. 갱신 가능한 URL은 다른 URL과 똑같이 발급되고, 해당 이미지를 그달의 차량 이미지로 집계하며, ttl_seconds 동안 유효합니다. 차이는 TTL 이후에 있습니다. 403 대신, 새로운 ttl_seconds 기간마다 첫 로드가 URL을 갱신하고 이미지를 제공합니다. 갱신 시 이미지는 해당 달에 한 번 집계되며, 그달에 이미 집계되었다면 추가로 집계되지 않습니다. 그 기간 내의 이후 로드는 첫 기간과 마찬가지로 공개 캐시가 가능합니다. 이는 최대 1년 뒤인 renews_until까지 계속됩니다.
이 설계에서 세 가지 특성이 생겨났고, 각각이 이메일에서 중요한 역할을 했습니다:
- 다시 보내거나 다시 발급할 필요가 없습니다. 이메일 속 URL은 영원히 같은 URL입니다. 저장된 링크를 다시 쓰는 cron 작업도, 새 이미지를 담은 두 번째 이메일도 필요 없습니다.
- 시간이 아니라 열람 횟수로 집계합니다. 아무도 열지 않는 기간에는 아무것도 집계되지 않습니다. 한 사람이 하루에 읽은 이메일은 그 이메일을 위해 발급된 차량 이미지 하나로 집계됩니다. 1년 동안 매주 열린 이메일은 열린 달마다 해당 이미지를 한 번씩, 최대 12번 집계합니다.
- 멱등 집계. 각 기간은 URL과 기간별로 고유한
renew:<url id>:<window>키로 한 번만 기록됩니다. 일주일에 50번 열리거나 두 메일 프록시가 첫 열람 시 동시에 요청해도 한 번으로 집계됩니다.
라이프사이클 이메일은 공개 엔드포인트와 동일한 코드 경로를 통해 이 URL을 플랫폼 토큰으로 발급합니다. 자사 이메일이므로 어떤 API 키에도 연결되지 않고 과금되지도 않지만, 동일한 GET /api/v1/delivery/{token} 경로에서 동일한 7일 유효 기간과 동일한 캐싱으로 제공됩니다. 첫 렌더 이메일의 이미지는 제품이 스스로를 시연하는 셈입니다.
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(최대값인 7일)는 이미지당 최대 주 1회 갱신을 뜻하며, 이미지는 얼마나 자주 갱신되든 한 달에 최대 한 번만 집계됩니다. renew: true와 함께 1시간 TTL을 사용해도 되지만, 자주 열리는 받은편지함에서는 열릴 때마다 매시간 갱신됩니다.이메일 본문
템플릿은 인라인 스타일을 사용한 React Email입니다. 메일 클라이언트가 스타일시트를 제거하기 때문입니다. 렌더는 부드러운 배경 위 테두리가 있는 표 셀에 배치되며, 레티나 화면에서도 선명하도록 480픽셀로 요청해 240픽셀로 표시하고, 위에는 알약 모양 배지, 아래에는 캡션 두 줄을 둡니다. 투명 PNG는 이 제품의 핵심이므로 그대로 유지합니다. 알파를 검은색으로 칠하는 구형 Outlook에서는 단색 셀 배경 위의 format: "jpg" 렌더가 대체 수단입니다.
<!-- 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>메시지의 나머지는 모두 텍스트입니다. 이 URL이 무엇인지, 왜 계속 작동하는지, 자신의 차량용 URL을 발급하는 정확한 POST 요청, 그리고 이 페이지로 연결되는 링크입니다. 이메일에는 수신 거부 링크와 List-Unsubscribe 헤더가 포함되어 있으며, 모든 제품 링크에 태그를 붙여 지금 읽고 계신 이 사례가 누군가를 다시 불러왔는지 유입 보고서에서 확인할 수 있습니다.
세 단계로 직접 해 보기
- 이메일을 작성할 때 서버 측에서 발급하세요. 차량당 한 번 호출하거나, 다이제스트라면 호출당 최대 50개까지 발급할 수 있습니다.
url과renews_until을 메시지와 함께 저장하세요. - URL을 너비, 높이, 대체 텍스트와 함께
<img>에 넣으세요. 대체 텍스트에는 "자동차"가 아니라 차량 정보("2024 Porsche 911, 빨간색, 전면 3/4 뷰")를 적습니다. - 계정을 사용 가능한 상태로 유지하세요. 갱신 시 이미지는 갱신된 달에 한 번 집계됩니다. 요금제의 차량 이미지를 모두 사용하면 매월 1일 허용량이 초기화될 때까지 402로 응답하므로, 제공하는 이미지 수에 맞게 요금제를 선택하세요.
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전체 매개변수 참조, 캐싱 헤더, 오류 코드는 서명된 URL 문서에서 확인할 수 있습니다. 플러그인과 함께 제공되는 에이전트 스킬도 같은 규칙을 알고 있으므로, "이 차를 뉴스레터에 넣어 줘"라는 요청을 받은 어시스턴트는 템플릿에 키를 붙여 넣는 대신 갱신 가능한 URL을 발급합니다.
자주 묻는 질문
- 이미지를 첨부하거나 base64로 인라인 삽입하지 않는 이유는 무엇인가요?
- 첨부 파일과 base64는 모든 메시지의 용량을 키웁니다. 서명된 URL은 수백 바이트에 불과하며, 일반 호스팅 이미지처럼 메일 클라이언트의 이미지 프록시를 통해 로드됩니다.
- 자동 갱신 URL의 비용은 얼마인가요?
- 생성 시 이번 달 차량 이미지 1개가 집계되고, 이후 renews_until까지 갱신 기간 중 실제로 열린 각 달에 같은 이미지가 한 번씩 집계됩니다. 아무도 이메일을 열지 않은 달에는 집계되지 않습니다. Pro는 월 US$49이며 매월 2,500개의 차량 이미지가 포함됩니다.
- 이번 달 차량 이미지를 모두 사용하면 어떻게 되나요?
- 로드 시 402가 반환되며, 이번 달 한도가 초기화되거나 플랜을 상향할 때까지 이미지가 깨진 상태로 표시됩니다. URL 자체는 유효하게 유지되며 다음 로드 시 다시 표시됩니다. 이번 달에 이미 집계된 이미지는 계속 제공됩니다.
- Gmail, Outlook, Apple Mail에서 작동하나요?
- 네. 전송 엔드포인트는 Cache-Control, ETag, CORS 헤더를 갖춘 일반 공개 GET으로, 이미지 프록시가 기대하는 형태 그대로입니다. PNG 투명도는 현재의 모든 메일 클라이언트에서 표시됩니다. 아주 오래된 Outlook 버전에서는 단색 배경에 format jpg로 요청하세요.
- URL을 무효화할 수 있나요?
- URL을 발급한 API 키를 폐기하면 해당 키로 발급된 모든 URL이 다음 로드 시 403을 반환하며, 갱신된 URL도 마찬가지입니다. 이후 공유 캐시는 최대 1시간 동안 계속 이미지를 제공할 수 있습니다.