مسئله: تصویری که باید در ماه مارس هم درست کار کند
هر حساب در Car Image API حدود یک ساعت پس از نخستین رندرش، یادداشت کوتاهی از جاناتان دریافت میکند. بدیهیترین چیزی که میشد در آن ایمیل گذاشت خود رندر است: دقیقاً همان Porsche 911 مدل ۲۰۲۴ یا Dodge Charger مدل ۲۰۰۶ که کاربر همین حالا درخواست کرده، بهعنوان گواهی بر اینکه یکپارچهسازی کار کرده و کمی هم برای خودنمایی.
ایمیل بدترین بستر ممکن برای یک URL امضاشده است. صفحه در هر بازدید دوباره رندر میشود؛ اما ایمیل یک بار رندر میشود و هر زمان که خواننده به آن برسد باز میشود، یک روز، یک ماه یا یک سال بعد، و اغلب از طریق پراکسی تصویر Gmail. URLهای امضاشدهٔ تحویل که API پیشتر ارائه میکرد حداکثر TTL هفتروزه داشتند؛ این سقف برای مرورگر مناسب است، اما تضمین میکند که تا هفتهٔ دوم، تصویر در صندوق ورودی خراب نمایش داده شود. پیوست کردن یا درج مستقیم PNG هم حجم هر پیام را زیاد میکند.
چیزی که میخواستیم یک URL بود که مثل یک تصویر میزبانیشده رفتار کند، تا هر زمان که کسی ممکن است ایمیل را باز کند کار کند، و همچنان برای آنچه واقعاً ارائه میدهد هزینهٔ منصفانهای از حساب کسر کند.
راهحل: URLهای امضاشده با تمدید خودکار
یک پرچم به POST /api/v1/image-urls اضافه کردیم: renew: true. URL تمدیدشونده دقیقاً مانند هر URL دیگری ساخته میشود، تصویرش را بهعنوان یک تصویر خودروی ماه میشمارد و به مدت ttl_seconds معتبر است. تفاوت در اتفاقی است که پس از پایان TTL میافتد. بهجای خطای 403، نخستین بارگذاری در هر بازه جدید ttl_seconds URL را تمدید میکند و تصویر را ارائه میدهد؛ هر تمدید تصویر را یک بار در ماهی که در آن رخ میدهد میشمارد و اگر تصویر آن ماه قبلاً شمرده شده باشد، چیزی اضافه نمیشود. هر بارگذاری بعدی در آن بازه، مانند بازه نخست، بهصورت عمومی قابل کش است. این روند تا renews_until ادامه مییابد که حداکثر تا یک سال بعد است.
سه ویژگی از دل این طراحی بیرون آمد و هر کدام برای ایمیل اهمیت داشت:
- نیازی به ارسال یا ساخت دوباره نیست. URL داخل ایمیل برای همیشه همان URL است. نه cron jobی برای بازنویسی پیوندهای ذخیرهشده، نه ایمیل دومی با تصویر تازه.
- شمارش بر اساس باز شدن است، نه زمان. در بازهای که کسی ایمیل را باز نکند، چیزی شمرده نمیشود. ایمیلی که یک نفر در یک روز بخواند، همان یک تصویر خودرویی است که برایش ساخته شده؛ ایمیلی که یک سال هر هفته باز شود، آن تصویر را در هر ماهی که باز شود یک بار میشمارد، حداکثر دوازده بار.
- شمارش بدون تکرار (idempotent). هر بازه یکبار ثبت میشود، با کلید
renew:<url id>:<window>که برای هر URL و بازه یکتاست. پنجاه بار باز شدن در یک هفته، یا رقابت دو پراکسی ایمیل بر سر نخستین باز شدن، فقط یکبار شمرده میشود.
ایمیلهای چرخهٔ عمر این URLها را از همان مسیر کدی میسازند که endpoint عمومی از آن استفاده میکند، بهصورت توکنهای پلتفرم: به هیچ کلید API وابسته نیستند و هرگز هزینهای برایشان محاسبه نمیشود، چون متعلق به خودمان هستند، اما از همان مسیر GET /api/v1/delivery/{token}، با همان بازههای هفتروزه و همان کشکردن ارائه میشوند. تصویر موجود در ایمیل نخستین رندر، خودِ محصول است که خودش را به نمایش میگذارد.
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 (هفت روز، حداکثر مقدار) یعنی حداکثر یک تمدید در هفته برای هر تصویر، و تصویر هر چند بار هم تمدید شود حداکثر یک بار در ماه شمرده میشود. TTL یکساعته با renew: true هم کار میکند، اما در صندوق ورودی پرتردد، تصویر در هر ساعتی که ایمیل باز شود تمدید میشود.خود ایمیل
قالب با React Email و استایلهای درونخطی ساخته شده است، چون کلاینتهای ایمیل استایلشیتها را حذف میکنند. رندر در یک خانهٔ جدول حاشیهدار روی پسزمینهای ملایم قرار دارد، با اندازهٔ ۴۸۰ پیکسل درخواست و با ۲۴۰ پیکسل نمایش داده میشود تا روی صفحهنمایشهای رتینا واضح بماند، با یک برچسب کپسولی بالای آن و دو خط توضیح زیر آن. 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 چیست، چرا همچنان کار میکند، درخواست POST دقیق برای ساختن یکی برای خودروی خودشان، و پیوندی به همین صفحه. ایمیل یک پیوند لغو اشتراک و سرآیندهای List-Unsubscribe دارد، و همه پیوندهای محصول برچسبگذاری شدهاند تا در گزارش جذب کاربر ببینیم آیا داستانی که میخوانید کسی را بازگردانده است یا نه.
خودتان در سه گام انجام دهید
- هنگام ساخت ایمیل، URL را در سمت سرور ایجاد کنید. یک درخواست برای هر خودرو، یا تا پنجاه خودرو در هر درخواست برای یک ایمیل خلاصه.
urlوrenews_untilرا همراه با پیام ذخیره کنید. - URL را همراه با عرض، ارتفاع و متن جایگزین (alt) در یک
<img>قرار دهید. متن alt نام خودرو را میآورد («2024 Porsche 911، قرمز، نمای سهرخ از جلو»)، نه فقط «خودرو». - حساب را شارژ نگه دارید. هر تمدید، تصویر را یک بار در ماهی که در آن رخ میدهد محاسبه میکند؛ اگر تصاویر خودروی پلن تمام شده باشد، تا بازنشانی سهمیه در روز اول ماه با 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 با تمدید خودکار چقدر است؟
- یک تصویر خودرو از سهمیهٔ ماه هنگام ساخت، سپس همان تصویر یک بار در هر ماه بعدی که پنجرهٔ تمدید واقعاً باز شود، تا renews_until؛ ماهی که کسی ایمیل را باز نکند، چیزی شمرده نمیشود. طرح Pro با $۴۹ در ماه، شامل ۲٬۵۰۰ تصویر خودرو در ماه است.
- وقتی تصاویر خودروی این ماه تمام شود، چه اتفاقی میافتد؟
- بارگذاری با کد 402 پاسخ میدهد و تصویر تا زمان بازنشانی سهمیهٔ ماه یا ارتقای طرح، خراب نمایش داده میشود؛ خود URL معتبر میماند و در بارگذاری بعدی دوباره کار میکند. تصویری که این ماه قبلاً شمرده شده است، همچنان ارائه میشود.
- آیا این روش در Gmail، Outlook و Apple Mail کار میکند؟
- بله. endpoint تحویل یک درخواست GET عمومی ساده با سرآیندهای Cache-Control، ETag و CORS است، که دقیقاً همان چیزی است که پراکسیهای تصویر انتظار دارند. شفافیت PNG در همه کلاینتهای فعلی نمایش داده میشود؛ برای نسخههای بسیار قدیمی Outlook، فرمت jpg را با پسزمینه یکدست درخواست کنید.
- آیا میتوان یک URL را باطل کرد؟
- کلید APIای را که آن را ایجاد کرده لغو کنید تا هر URL ساختهشده با آن کلید، از جمله URLهای تمدیدشده، در بارگذاری بعدی پاسخ 403 بدهد. حافظههای cache مشترک پس از آن حداکثر تا یک ساعت همچنان تصویر را ارائه میدهند.