Car Image API

ลูกค้า · Car Image API

วิธีฝังภาพรถยนต์ในอีเมลด้วย signed URL ที่ไม่มีวันเสีย

Car Image API ส่งภาพเรนเดอร์แรกของลูกค้าทุกคนไปยังกล่องจดหมายด้วย signed URL ที่ต่ออายุอัตโนมัติ: กลไก การคำนวณ และโค้ดสำหรับทำเอง

ปัญหา: ภาพที่ต้องยังใช้งานได้ในเดือนมีนาคม

ทุกบัญชีบน 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} เดียวกัน มีช่วงอายุเจ็ดวันเหมือนกัน และแคชแบบเดียวกันทุกประการ รูปภาพในอีเมลการเรนเดอร์ครั้งแรกจึงเป็นผลิตภัณฑ์ที่กำลังสาธิตตัวเอง

สร้าง URL ที่ต่ออายุได้หนึ่งรายการสำหรับภาพเรนเดอร์แรกของลูกค้า
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…"
}

ตัวอีเมล

เทมเพลตใช้ React Email พร้อมสไตล์แบบอินไลน์ เพราะโปรแกรมอีเมลจะตัดสไตล์ชีตออก ภาพเรนเดอร์อยู่ในเซลล์ตารางที่มีขอบบนพื้นหลังสีอ่อน ขอภาพที่ขนาด 480 พิกเซลและแสดงที่ 240 พิกเซล เพื่อให้คมชัดบนหน้าจอ Retina โดยมีป้ายทรงแคปซูลอยู่ด้านบนและคำบรรยายสองบรรทัดอยู่ด้านล่าง PNG แบบโปร่งใสคือหัวใจของผลิตภัณฑ์ เราจึงคงไว้ ส่วน Outlook รุ่นเก่าที่แสดงช่องอัลฟาเป็นสีดำ ทางสำรองคือภาพเรนเดอร์ format: "jpg" บนเซลล์ที่มีพื้นหลังสีทึบ

การ์ดในรูปแบบ HTML ธรรมดา
<!-- 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 ขั้นตอน

  1. สร้าง URL ฝั่งเซิร์ฟเวอร์ขณะสร้างอีเมล เรียกหนึ่งครั้งต่อรถหนึ่งคัน หรือสูงสุดห้าสิบคันต่อการเรียกหนึ่งครั้งสำหรับอีเมลสรุป จัดเก็บ url และ renews_until ไว้พร้อมกับข้อความ
  2. ใส่ URL ใน <img> พร้อมระบุความกว้าง ความสูง และข้อความ alt ข้อความ alt ควรระบุตัวรถ ("2024 Porsche 911 สีแดง มุมหน้าเฉียงสามในสี่") ไม่ใช่แค่ "รถ"
  3. รักษาบัญชีให้มีโควตาเพียงพอ การต่ออายุจะนับภาพหนึ่งครั้งในเดือนที่เกิดขึ้น หากภาพรถของแพ็กเกจถูกใช้หมดแล้ว ระบบจะตอบกลับ 402 จนกว่าโควตาจะรีเซ็ตในวันที่ 1 ดังนั้นควรเลือกแพ็กเกจให้เหมาะกับจำนวนภาพที่คุณให้บริการ
ใช้ TypeScript SDK
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 ที่ต่ออายุแล้วด้วย แคชที่ใช้ร่วมกันจะยังคงส่งภาพได้อีกไม่เกินหนึ่งชั่วโมงหลังจากนั้น