المشكلة: صورة يجب أن تظل تعمل في مارس
يتلقى كل حساب على Car Image API رسالة قصيرة من جوناثان بعد نحو ساعة من أول صورة يُنشئها. والشيء البديهي الذي يُوضع في تلك الرسالة هو الصورة نفسها: سيارة Porsche 911 موديل 2024 أو Dodge Charger موديل 2006 التي طلبها الشخص للتو تحديدًا، كدليل على نجاح التكامل وكنوع من التباهي البسيط.
البريد الإلكتروني هو أسوأ مكان ممكن لعنوان URL موقّع. فالصفحة يُعاد عرضها في كل زيارة؛ أما البريد الإلكتروني فيُعرض مرة واحدة ويُفتح متى ما وصل إليه القارئ، بعد يوم أو شهر أو سنة، وغالبًا عبر وكيل الصور في Gmail. وكانت عناوين URL الموقّعة للتسليم التي توفّرها API سابقًا ذات مدة TTL قصوى تبلغ سبعة أيام، وهو الحد المناسب للمتصفح، لكنه يضمن ظهور صورة معطّلة في صندوق الوارد بحلول الأسبوع الثاني. أما إرفاق ملف PNG أو تضمينه مباشرةً فيضخّم حجم كل رسالة.
ما أردناه هو عنوان URL يتصرّف مثل صورة مستضافة، ويظل يعمل طالما قد يفتح أحدٌ الرسالة، ومع ذلك يحاسب الحساب بإنصاف على ما يقدّمه فعلًا.
الحل: عناوين URL موقّعة تتجدد تلقائيًا
أضفنا خيارًا واحدًا إلى POST /api/v1/image-urls: renew: true. يُنشأ الرابط القابل للتجديد ويُحتسب تمامًا مثل أي رابط آخر: رصيد واحد، وصالح لمدة ttl_seconds. الفرق فيما يحدث بعد انتهاء TTL. فبدلًا من الخطأ 403، يُحتسب على الحساب المُصدِر رصيد واحد عند أول تحميل في كل فترة جديدة مدتها ttl_seconds وتُعرض الصورة؛ وكل تحميل لاحق في تلك الفترة مجاني وقابل للتخزين المؤقت العام، تمامًا كالفترة الأولى. ويستمر ذلك حتى renews_until، لمدة تصل إلى عام.
نتجت عن هذا التصميم ثلاث خصائص، وكانت كل منها مهمة للبريد الإلكتروني:
- لا حاجة لإعادة الإرسال أو إعادة الإنشاء. الرابط في البريد هو الرابط إلى الأبد. لا مهام cron تعيد كتابة الروابط المخزنة، ولا رسالة ثانية بصورة جديدة.
- ادفعوا مقابل الفتح، لا مقابل الوقت. الفترة التي لا يفتح فيها أحدٌ الرسالة لا تكلّف شيئًا. والرسالة التي يقرؤها شخص واحد في يوم واحد تكلّف وحدة الرصيد الواحدة التي أُصدر الرابط من أجلها؛ أما رسالة تُفتح كل أسبوع لمدة عام فتكلّف نحو 52 وحدة رصيد، أي خمسة سنتات.
- فوترة بلا ازدواج. كل نافذة صلاحية هي صف في السجل المحاسبي بالمفتاح
renew:<url id>:<window>، فريد لكل URL ونافذة. خمسون فتحًا في أسبوع، أو خادما بريد وسيطان يتسابقان عند الفتح الأول، تُدفع كلها مرة واحدة فقط.
تُنشئ رسائل دورة الحياة هذه العناوين عبر مسار الشيفرة نفسه الذي تستخدمه نقطة النهاية العامة، كرموز منصة: غير مرتبطة بأي مفتاح 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 مع أنماط مضمّنة، لأن برامج البريد تحذف أوراق الأنماط. تقع الصورة في خلية جدول ذات حدود على خلفية هادئة، وتُطلب بعرض 480 بكسل وتُعرض بعرض 240 لتبقى حادة على شاشات Retina، مع شارة فوقها وسطرين من التعليق أسفلها. الخلفية الشفافة بصيغة 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وrenews_untilمع الرسالة. - ضعوا URL في
<img>مع العرض والارتفاع والنص البديل. يذكر النص البديل اسم المركبة ("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 يتجدد تلقائيًا؟
- رصيد واحد عند إنشائه، ثم رصيد واحد لكل فترة إضافية مدتها ttl_seconds يُفتح خلالها فعليًا، حتى renews_until. ومع مدة TTL تبلغ سبعة أيام، يعني ذلك رصيدًا واحدًا كحد أقصى أسبوعيًا لكل صورة، ولا شيء في الأسابيع التي لا يفتح فيها أحد البريد. ويمنحكم 1 US$ عدد 100 من الأرصدة.
- ماذا يحدث إذا نفد رصيد الحساب؟
- يُرجع التحميل الخطأ 402 Insufficient credits وتظهر الصورة معطوبة حتى يُعاد شحن الرصيد؛ أما عنوان URL نفسه فيبقى صالحًا ويعود للعمل في التحميل التالي. وتمنع ميزة إعادة الشحن التلقائي في لوحة التحكم حساب الإنتاج من الوصول إلى هذا الحد أبدًا.
- هل يعمل هذا في Gmail وOutlook وApple Mail؟
- نعم. نقطة التسليم طلب GET عام بسيط مع ترويسات Cache-Control وETag وCORS، وهذا بالضبط ما تتوقعه خوادم وكيل الصور. تظهر شفافية PNG في جميع برامج البريد الحالية؛ أما لإصدارات Outlook القديمة جدًا فاطلب التنسيق jpg بخلفية بلون ثابت.
- هل يمكن إلغاء URL؟
- ألغوا مفتاح API الذي أنشأ العنوان، وسيستجيب كل عنوان URL صادر عن ذلك المفتاح برمز 403 عند تحميله التالي، بما في ذلك العناوين المجدَّدة. وتستمر ذاكرات التخزين المؤقت المشتركة في تقديم الصورة لمدة ساعة كحد أقصى بعد ذلك.