Các cách nhúng ảnh vào nội dung email và hiển thị ảnh nhúng trong nội dung email sử dụng Outlook REST API

Ảnh nhúng xuất hiện rất nhiều trong email, đặc biệt là các email thương mại, quảng cáo ... nhằng tăng nội dung cũng như thu hút người đọc chú ý hơn. Việc nhúng ảnh vào trong nội dung mail chúng ta thường dùng hầu hết là do công cụ soạn thảo email hỗ trợ, nếu muốn nắm rõ cách thức hoạt động, bài viết này sẽ giúp bạn sáng tỏ phần nào.
Theo tài liệu tham khảo từ SendGrid, ta sẽ có 3 cách thức nhúng ảnh :

  • Linked Images
    Đây là cách thức đơn giản nhất, sử dụng thẻ img, xem nội dung email dưới dạng HTML, ta sẽ có đoạn code dạng :
<img src="http://www.abc.com/images/aaa.jpg">
  • CID Embedded Images (Inline Images)
    CID là viết tắt của Content-ID, bức ảnh sẽ được lưu lại trên máy chủ gửi mail, và chúng ta sẽ có mã số tương ứng với bức ảnh đó, trong nội dung email dạng HTML chúng ta sẽ đoạn code tương ứng :
<img src="cid:Image-CID">

Vẫn là thẻ img huyền thoại, tuy nhiên nội dung trong src lại là một dãy ký tự chứ không phải là đường link.

  • Inline Embedding (Base64 Encoding)
    Với cách này, ảnh sẽ được encode sang base64, sau đó nhúng trực tiếp vào HTML, xem nội dung mail dạng HTML ta sẽ có dạng code :
<img src="data:image/png;base64,{ContentBytes}"/>

Trong đó ContentBytes chính là nội dung bức ảnh sau khi encode base64.

Ta có thể thấy cách đầu tiên, bức ảnh không phải là một phần của nội dung mail, nó có thể được lưu trữ ở bất cứ đâu và ta chỉ cần đường link của nó để chèn vào mail. Ưu điểm của nó sẽ giúp ta tiết kiệm được dung lượng lưu trữ cũng như dung lượng của mail cũng nhẹ nhàng hơn, tuy nhiên nhược điểm của nó là chúng ta phải phụ thuộc vào nơi lưu trữ bức ảnh, nơi đó có thể xóa bức ảnh bất cứ lúc nào mà ta không thể can thiệp được. 2 cách tiếp theo giải quyết được vấn đề bị động quản lý ảnh, với 2 cách này, ta có thể coi ảnh là một phần của nội dung mail, ta hoàn toàn chủ động trong việc giữ hay xóa ảnh. Đặc điểm của CID Embedded Images là bức ảnh vẫn được lưu trên máy chủ và tách riêng với nội dung mail, trong khi đó Inline Embedding (Base64 Encoding) thì nội dung của bức ảnh dạng base64 nằm trong nội dung của mail luôn, dẫn đến dung lượng của mail sẽ lớn hơn.

Trên đây là phần lý thuyết giúp ta hiểu được những cách nhúng/chèn ảnh vào nội dung mail, cũng như khi đọc nội dung mail dạng HTML sẽ hiểu được các thẻ img. Vấn đề này mình tìm hiểu do trong quá trình xây dựng mail client bằng Outlook REST API, nội dung mail nhận về chưa những thẻ img với src chứa cid, và Outlook không hề hỗ trợ việc lấy lại ảnh trực tiếp ra ngoài nội dung mail. Sau khi tìm hiểu mò mẫm, mình để ý thấy những mail chứa ảnh nhúng, khi lấy danh sách attachment của mail đó, Outlook cũng sẽ trả về các ảnh dưới dạng attachments thông thường nhưng có thể thuộc tính IsInline: true giúp ta phân biệt ảnh nhúng và attachments thông thường. Điểm khá ngố ở Outlook REST API là khi lấy nội dung mail về, mặc dù có ảnh nhung, nhưng thuộc tính HasAttachments: false vẫn được trả về, ta vẫn cố tình gửi request lấy danh sách attachments thì vẫn nhận được đầy đủ danh sách ảnh nhúng. 😄. Vì vậy hiện tại, mình phải kiểm tra nội dung mail có chứa dạng thẻ img với src="cid:..." thì mình sẽ gửi request lấy attachments. Để xác định được thẻ img đặc biệt này, ta có thể viết regex để kiểm tra và thay thế, tuy nhiên mình nghĩ dùng sẵn gem Nokogiri trong Rails thì sẽ đơn giản hơn cho việc tìm cũng như thay thế. Nói tiếp tới việc ta có thể lấy được danh sách ảnh nhúng, khi gửi request lấy dữ liệu của chúng, ta sẽ nhận được nội dung bức ảnh đó dạng base64, vậy ta sẽ sử dụng Inline Embedding (Base64 Encoding) để chèn lại ảnh vào nội dung mail. Đây là đoạn code mình xử lý việc tìm thẻ img đặc biệt và thay thế chúng bởi base64.

INLINE_IMAGE_PREFIX = "data:image/jpeg;base64,".freeze
CID_PREFIX = "cid:".freeze
def filling_cid outlook, message_object
  inline_images = MailAttachment
    .get_attachments(outlook, message_object.id)[:inline_images]
  message_body = Nokogiri::HTML message_object.body
  message_body.search("img").each do |element|
    if inline_images.present? && element[:src].include?(CID_PREFIX)
      inline_image = inline_images.shift
      inline_image_content_bytes = MailAttachment.get_attachment(outlook,
        message_object.id, inline_image.id).content_bytes
      image_src = INLINE_IMAGE_PREFIX + inline_image_content_bytes
      element[:src] = image_src
    end
  end
  message_object.body = message_body.to_html
  message_object
end

Do kiến thức còn hạn chế nên bài viết có thể sai sót, mong mọi người nhiệt tình góp ý để mình hoàn thiện kiến thức cũng như viết bài tốt hơn.
Tài liệu tham khảo: