Postmark inline images with Base64 and CID
To put an image inside a Postmark HTML email, send the image bytes as a Base64 attachment and reference that attachment from the HTML. The attachment is part of the email; the <img> element does not contain the Base64 string itself.
If you already have an image file, the Image to Base64 tool can give you the raw Base64 value without uploading the file. Select raw Base64 for the API payload. A full data:image/png;base64,... URL belongs in a browser document, not in Postmark's Attachments.Content field.
The two values that must match
Postmark's official inline-image example, checked on October 8, 2026, uses a ContentID such as cid:logo001. The HTML uses the same value: <img src="cid:logo001" alt="Company logo">.
The attachment fields describe different things:
| Field | Value |
|---|---|
Name | A filename, such as logo.png |
Content | Raw Base64 of the image bytes |
ContentType | The MIME type, such as image/png |
ContentID | The CID reference used by the HTML, including cid: in Postmark's documented format |
Do not copy a different provider's attachment schema unchanged. For example, SendGrid inline images use different field names and a different convention for content_id.
Build the payload in Node.js
Save this as postmark-inline.mjs. It requires Node.js 18 or later and a PNG named logo.png. Running it normally prints a reviewable payload. Passing --send makes an actual API request, so use a verified sender, the intended recipient, and a server token only when you are ready to send.
import { readFile } from "node:fs/promises";
const bytes = await readFile("logo.png");
const cid = "cid:logo001";
const payload = {
From: process.env.EMAIL_FROM || "sender@example.com",
To: process.env.EMAIL_TO || "recipient@example.com",
Subject: "Inline image example",
HtmlBody: `<p>Your image:</p><img src="${cid}" alt="Company logo">`,
TextBody: "This message includes an image in the HTML version.",
Attachments: [{
Name: "logo.png",
Content: bytes.toString("base64"),
ContentType: "image/png",
ContentID: cid
}]
};
if (!process.argv.includes("--send")) {
console.log(JSON.stringify(payload, null, 2));
} else {
const token = process.env.POSTMARK_SERVER_TOKEN;
if (!token || !process.env.EMAIL_FROM || !process.env.EMAIL_TO) {
throw new Error("Set POSTMARK_SERVER_TOKEN, EMAIL_FROM, and EMAIL_TO first.");
}
const response = await fetch("https://api.postmarkapp.com/email", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"X-Postmark-Server-Token": token
},
body: JSON.stringify(payload)
});
const result = await response.json();
if (!response.ok || result.ErrorCode !== 0) {
throw new Error(`Postmark request failed: ${JSON.stringify(result)}`);
}
console.log(result.MessageID);
}
The MIME type must match the file you read. For a JPEG, change the filename and use image/jpeg. Do not relabel JPEG bytes as PNG. Keep real tokens in environment variables rather than the source file, and review the payload before sending.
Raw Base64, data URI, and CID are different
Raw Base64 is the encoded file content. A data URI adds a media type and prefix, for example data:image/png;base64,.... A CID is a reference to an attachment in a MIME message.
If your upstream code returns a data URI, remove its header before placing the content in Attachments.Content. If your upstream code already returns raw Base64, use it directly. Encoding that text a second time produces Base64 of the string rather than Base64 of the original image bytes. The API payload guide explains this boundary in more detail.
Troubleshooting an image that does not render
Check the CID match first. Check that Content has no data: prefix, that its decoded bytes form the expected file, and that ContentType matches those bytes. Use a small image while debugging so the payload is easy to inspect.
Client behavior still matters after a valid API request. An accepted message does not prove that every mail client displayed the image. Test the actual clients your recipients use; the Outlook Base64 image guide explains why a data URI inside HTML is a separate compatibility problem.
FAQ
Can I paste a data URI directly into HtmlBody?
CID attachments are the documented pattern here. Email clients have uneven support for data URIs, so a browser preview is not a mail-client test.
Does the Postmark token belong in the browser?
No. Send through a server you control. A browser-only converter can prepare the Base64 value without holding a mail-service credential.
Will every client show the image inline?
That requires client testing. A successful API response confirms acceptance by the API, not inbox placement or rendering.
Related guides
Use SendGrid CID attachments for a provider-specific comparison, image-to-Base64 API payloads for output shapes, and Outlook image troubleshooting for client behavior.