ปัญหา: ภาพที่ต้องยังใช้งานได้ในเดือนมีนาคม
ทุกบัญชีบน Car Image API จะได้รับข้อความสั้นๆ จาก Jonathan ประมาณหนึ่งชั่วโมงหลังการเรนเดอร์ครั้งแรก สิ่งที่เหมาะจะใส่ในอีเมลนั้นที่สุดก็คือภาพเรนเดอร์นั่นเอง: Porsche 911 ปี 2024 หรือ Dodge Charger ปี 2006 ตรงตามที่ผู้ใช้เพิ่งขอ เพื่อเป็นหลักฐานว่าการผสานระบบใช้งานได้ และเพื่ออวดเล็กน้อย
อีเมลเป็นช่องทางที่แย่ที่สุดสำหรับ signed URL หน้าเว็บจะถูกเรนเดอร์ใหม่ทุกครั้งที่เข้าชม แต่อีเมลถูกเรนเดอร์เพียงครั้งเดียวและถูกเปิดเมื่อใดก็ตามที่ผู้อ่านสะดวก อาจเป็นหนึ่งวัน หนึ่งเดือน หรือหนึ่งปีต่อมา และมักผ่านพร็อกซีรูปภาพของ Gmail signed URL สำหรับส่งภาพที่ API มีอยู่แล้วมี TTL สูงสุดเจ็ดวัน ซึ่งเป็นเพดานที่เหมาะสำหรับเบราว์เซอร์ แต่รับประกันได้เลยว่าภาพในกล่องจดหมายจะเสียภายในสัปดาห์ที่สอง ส่วนการแนบหรือฝัง PNG ไว้ในเนื้อหาก็ทำให้ทุกข้อความมีขนาดใหญ่เกินจำเป็น
สิ่งที่เราต้องการคือ URL ที่ทำงานเหมือนภาพที่โฮสต์ไว้ ใช้งานได้ต่อเนื่องตราบเท่าที่ยังอาจมีคนเปิดอีเมล และยังคิดค่าบริการกับบัญชีอย่างเป็นธรรมตามที่ให้บริการจริง
ทางออก: signed URL ที่ต่ออายุอัตโนมัติ
เราเพิ่มแฟล็กหนึ่งตัวใน POST /api/v1/image-urls: renew: true URL ที่ต่ออายุได้จะถูกสร้างเหมือน URL อื่นทุกประการ นับภาพเป็นภาพรถยนต์ของเดือน และใช้งานได้ตาม ttl_seconds ความแตกต่างคือสิ่งที่เกิดขึ้นหลัง TTL หมด แทนที่จะได้ 403 การโหลดครั้งแรกในแต่ละช่วงใหม่ของ ttl_seconds จะต่ออายุ URL และแสดงภาพ การต่ออายุจะนับภาพหนึ่งครั้งในเดือนที่เกิดการต่ออายุ และไม่นับเพิ่มหากนับไปแล้วในเดือนนั้น การโหลดครั้งต่อ ๆ ไปในช่วงนั้นสามารถแคชแบบสาธารณะได้เช่นเดียวกับช่วงแรก และจะเป็นเช่นนี้ต่อไปจนถึง renews_until ซึ่งกำหนดได้ไกลสูงสุดหนึ่งปี
การออกแบบนี้ทำให้เกิดคุณสมบัติสามประการ และแต่ละข้อล้วนสำคัญต่ออีเมล:
- ไม่ต้องส่งซ้ำหรือสร้างใหม่ URL ในอีเมลคือ URL ที่ใช้ได้ตลอดไป ไม่ต้องมี cron job คอยเขียนลิงก์ที่จัดเก็บไว้ใหม่ ไม่ต้องส่งอีเมลฉบับที่สองพร้อมภาพใหม่
- นับตามการเปิด ไม่ใช่ตามเวลา ช่วงเวลาที่ไม่มีใครเปิดจะไม่นับเลย อีเมลที่อ่านโดยคนหนึ่งคนในวันเดียวคือภาพรถยนต์ 1 ภาพที่สร้างไว้ ส่วนอีเมลที่เปิดทุกสัปดาห์ตลอดหนึ่งปีจะนับภาพนั้นหนึ่งครั้งในแต่ละเดือนที่มีการเปิด สูงสุดสิบสองครั้ง
- การนับแบบ idempotent แต่ละช่วงเวลาจะถูกบันทึกเพียงครั้งเดียว โดยใช้คีย์
renew:<url id>:<window>ซึ่งไม่ซ้ำกันต่อ URL และช่วงเวลา การเปิดห้าสิบครั้งในหนึ่งสัปดาห์ หรือพร็อกซีอีเมลสองตัวที่แข่งกันเปิดครั้งแรก จะนับเป็นครั้งเดียว
อีเมลตามวงจรชีวิตลูกค้าสร้าง URL เหล่านี้ผ่านเส้นทางโค้ดเดียวกับที่ endpoint สาธารณะใช้ ในรูปแบบโทเค็นของแพลตฟอร์ม: ไม่ผูกกับ API key ใดและไม่มีการเรียกเก็บเงิน เพราะเป็นของเราเอง แต่ให้บริการผ่านเส้นทาง 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 ที่ใช้สร้าง URL สำหรับรถของผู้รับเอง และลิงก์มายังหน้านี้ อีเมลมีลิงก์ยกเลิกการรับข่าวสารและเฮดเดอร์ List-Unsubscribe และลิงก์ผลิตภัณฑ์ทุกลิงก์มีการติดแท็กไว้ เพื่อให้เราเห็นในรายงานการหาผู้ใช้ใหม่ว่าเรื่องราวที่คุณกำลังอ่านนี้ดึงใครกลับมาบ้างหรือไม่
ทำด้วยตนเองใน 3 ขั้นตอน
- สร้าง URL ฝั่งเซิร์ฟเวอร์ขณะสร้างอีเมล เรียกหนึ่งครั้งต่อรถหนึ่งคัน หรือสูงสุดห้าสิบคันต่อการเรียกหนึ่งครั้งสำหรับอีเมลสรุป จัดเก็บ
urlและrenews_untilไว้พร้อมกับข้อความ - ใส่ URL ใน
<img>พร้อมระบุความกว้าง ความสูง และข้อความ alt ข้อความ alt ควรระบุตัวรถ ("2024 Porsche 911 สีแดง มุมหน้าเฉียงสามในสี่") ไม่ใช่แค่ "รถ" - รักษาบัญชีให้มีโควตาเพียงพอ การต่ออายุจะนับภาพหนึ่งครั้งในเดือนที่เกิดขึ้น หากภาพรถของแพ็กเกจถูกใช้หมดแล้ว ระบบจะตอบกลับ 402 จนกว่าโควตาจะรีเซ็ตในวันที่ 1 ดังนั้นควรเลือกแพ็กเกจให้เหมาะกับจำนวนภาพที่คุณให้บริการ
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ข้อมูลอ้างอิงพารามิเตอร์ทั้งหมด เฮดเดอร์การแคช และรหัสข้อผิดพลาดอยู่ในเอกสาร signed URL agent skill ที่มาพร้อมปลั๊กอินรู้กฎเดียวกันนี้ ดังนั้นเมื่อผู้ช่วย AI ได้รับคำขอให้ "ใส่รถคันนี้ลงในจดหมายข่าว" ก็จะสร้าง URL ที่ต่ออายุได้ แทนการวางคีย์ลงในเทมเพลต
คำถามที่พบบ่อย
- ทำไมไม่แนบรูปภาพหรือฝังเป็น base64 ในข้อความ
- ไฟล์แนบและ base64 ทำให้ทุกข้อความมีขนาดใหญ่ขึ้น ส่วน signed URL มีขนาดเพียงไม่กี่ร้อยไบต์ และโหลดผ่านพร็อกซีรูปภาพของโปรแกรมอีเมลเหมือนรูปภาพที่โฮสต์ไว้ทั่วไป
- URL ที่ต่ออายุอัตโนมัติมีค่าใช้จ่ายเท่าไร
- นับเป็นภาพรถยนต์ 1 ภาพของเดือนเมื่อสร้างขึ้น จากนั้นภาพเดิมนั้นจะนับหนึ่งครั้งในแต่ละเดือนถัดไปที่มีการเปิดในช่วงต่ออายุจริง จนถึง renews_until เดือนที่ไม่มีใครเปิดอีเมลจะไม่นับเลย แพ็กเกจ Pro ราคา US$49 ต่อเดือน รวมภาพรถยนต์ 2,500 ภาพต่อเดือน
- จะเกิดอะไรขึ้นเมื่อใช้ภาพรถยนต์ของเดือนครบแล้ว
- การโหลดจะตอบกลับด้วย 402 และภาพจะแสดงเป็นภาพเสียจนกว่าโควตาของเดือนจะรีเซ็ตหรือมีการอัปเกรดแพ็กเกจ ตัว URL ยังคงใช้ได้และจะกลับมาแสดงผลในการโหลดครั้งถัดไป ภาพที่ถูกนับแล้วในเดือนนี้ยังคงแสดงได้ตามปกติ
- ใช้งานได้ใน Gmail, Outlook และ Apple Mail หรือไม่
- ได้ ปลายทางสำหรับส่งภาพเป็น GET สาธารณะธรรมดาที่มีเฮดเดอร์ Cache-Control, ETag และ CORS ซึ่งตรงกับสิ่งที่พร็อกซีรูปภาพต้องการ ความโปร่งใสของ PNG แสดงผลได้ในไคลเอนต์อีเมลปัจจุบันทุกตัว สำหรับ Outlook เวอร์ชันเก่ามาก ให้ขอ format jpg บนพื้นหลังสีทึบ
- สามารถเพิกถอน URL ได้หรือไม่
- เพิกถอน API key ที่สร้าง URL นั้น แล้วทุก URL จากคีย์นั้นจะตอบกลับ 403 ในการโหลดครั้งถัดไป รวมถึง URL ที่ต่ออายุแล้วด้วย แคชที่ใช้ร่วมกันจะยังคงส่งภาพได้อีกไม่เกินหนึ่งชั่วโมงหลังจากนั้น