Adding attachments
Theattachments array attaches files to your email. Each entry is an object with the MIME type of
the file, the filename shown to the recipient, and content containing the file’s bytes as a
base64-encoded string. See Embedding inline images below for the optional
content_id field.
#!/usr/bin/env bash
set -u
: "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}"
: "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}"
: "${TO_EMAIL:?Set TO_EMAIL}"
: "${ATTACHMENT_PATH:?Set ATTACHMENT_PATH to the file you want to attach, e.g. ./logo.png}"
# Base64-encode the file. The -w0 flag (GNU base64) emits a single line with no
# wrapping; on macOS, drop -w0 — `base64` there doesn't wrap by default.
ATTACHMENT_BASE64=$(base64 -w0 "$ATTACHMENT_PATH")
ATTACHMENT_FILENAME=$(basename "$ATTACHMENT_PATH")
curl -X POST https://api.mailchannels.net/tx/v1/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: $MAILCHANNELS_API_KEY" \
-d @- <<JSON
{
"personalizations": [
{ "to": [{ "email": "$TO_EMAIL", "name": "Recipient" }] }
],
"from": {
"email": "$FROM_EMAIL",
"name": "Your Name"
},
"subject": "Hello from MailChannels",
"content": [
{
"type": "text/plain",
"value": "Hello! See the attached file."
}
],
"attachments": [
{
"type": "image/png",
"filename": "$ATTACHMENT_FILENAME",
"content": "$ATTACHMENT_BASE64"
}
]
}
JSON
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import { MailChannels } from "mailchannels-sdk";
const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL, ATTACHMENT_PATH } = process.env;
if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running");
if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)");
if (!TO_EMAIL) throw new Error("Set TO_EMAIL");
if (!ATTACHMENT_PATH) throw new Error("Set ATTACHMENT_PATH to the file you want to attach, e.g. ./logo.png");
const fileBytes = await readFile(ATTACHMENT_PATH);
const mailchannels = new MailChannels(MAILCHANNELS_API_KEY);
const { data, error } = await mailchannels.emails.send({
from: { email: FROM_EMAIL, name: "Your Name" },
to: { email: TO_EMAIL, name: "Recipient" },
subject: "Hello from MailChannels",
text: "Hello! See the attached file.",
attachments: [
{
type: "image/png",
filename: basename(ATTACHMENT_PATH),
content: fileBytes.toString("base64"),
},
],
});
if (error) {
console.error("Send failed:", error);
process.exit(1);
}
console.log(data);
import os
import sys
from pathlib import Path
import mailchannels
if not os.environ.get("MAILCHANNELS_API_KEY"):
sys.exit("Set MAILCHANNELS_API_KEY before running")
from_email = os.environ.get("FROM_EMAIL")
to_email = os.environ.get("TO_EMAIL")
attachment_path = os.environ.get("ATTACHMENT_PATH")
if not from_email:
sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)")
if not to_email:
sys.exit("Set TO_EMAIL")
if not attachment_path:
sys.exit("Set ATTACHMENT_PATH to the file you want to attach, e.g. ./logo.png")
# Attachment.from_bytes base64-encodes the bytes and guesses the content type
# from the filename.
attachment = mailchannels.Attachment.from_bytes(
Path(attachment_path).read_bytes(),
filename=Path(attachment_path).name,
)
response = mailchannels.Emails.send(
{
"from": {"email": from_email, "name": "Your Name"},
"to": [{"email": to_email, "name": "Recipient"}],
"subject": "Hello from MailChannels",
"text": "Hello! See the attached file.",
"attachments": [attachment],
}
)
print(response)
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MailChannels\Client;
$api_key = getenv('MAILCHANNELS_API_KEY') or exit("Error: MAILCHANNELS_API_KEY is not set\n");
$from_email = getenv('FROM_EMAIL') or exit("Error: FROM_EMAIL is not set\n");
$to_email = getenv('TO_EMAIL') or exit("Error: TO_EMAIL is not set\n");
$attachment_path = getenv('ATTACHMENT_PATH') or exit("Error: ATTACHMENT_PATH is not set\n");
$client = new Client(apiKey: $api_key);
$content = file_get_contents($attachment_path);
if ($content === false) {
exit("Error: could not read file: {$attachment_path}\n");
}
$mime = mime_content_type($attachment_path) ?: 'application/octet-stream';
$response = $client->emails->send([
'from' => ['email' => $from_email, 'name' => 'Your Name'],
'to' => ['email' => $to_email, 'name' => 'Recipient'],
'subject' => 'Hello from MailChannels',
'text' => 'Hello! See the attached file.',
'attachments' => [
[
'content' => base64_encode($content),
'filename' => basename($attachment_path),
'type' => $mime,
],
],
]);
print_r($response->toArray());
Limits
- Up to 1000 attachments per email.
- 30 MB combined limit on attachments and email content. Messages that exceed this are rejected.
Forbidden attachments
The following MIME types and file extensions are blocked because they are commonly associated with malware. Sending an attachment with a forbidden type or extension returns an error. MIME typesapplication/x-msdownloadapplication/vnd.ms-htmlhelpapplication/java-archiveapplication/x-shapplication/x-shellscript
.ade, .adp, .bat, .chm, .cmd, .com, .cpl, .exe, .hta, .ins, .isp, .jar, .jse,
.lib, .lnk, .mde, .msc, .msp, .mst, .pif, .scr, .sct, .sh, .shb, .sys, .vb,
.vbe, .vbs, .vxd, .wsc, .wsf, .wsh
Embedding inline images
Settingcontent_id on an attachment embeds it inline in the message body instead of showing it as a
downloadable attachment. Reference the image from HTML content with a cid: URI that matches the
content_id, for example <img src="cid:logo"> for an attachment with content_id: "logo".
#!/usr/bin/env bash
set -u
: "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}"
: "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}"
: "${TO_EMAIL:?Set TO_EMAIL}"
: "${LOGO_PATH:?Set LOGO_PATH to the image you want to embed, e.g. ./logo.png}"
# Base64-encode the image. The -w0 flag (GNU base64) emits a single line with no
# wrapping; on macOS, drop -w0 — `base64` there doesn't wrap by default.
LOGO_BASE64=$(base64 -w0 "$LOGO_PATH")
curl -X POST https://api.mailchannels.net/tx/v1/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: $MAILCHANNELS_API_KEY" \
-d @- <<JSON
{
"personalizations": [
{ "to": [{ "email": "$TO_EMAIL", "name": "Recipient" }] }
],
"from": {
"email": "$FROM_EMAIL",
"name": "Your Name"
},
"subject": "Hello from MailChannels",
"content": [
{
"type": "text/html",
"value": "<p>Hello! Here's our logo: <img src=\"cid:logo\"></p>"
}
],
"attachments": [
{
"type": "image/png",
"filename": "logo.png",
"content": "$LOGO_BASE64",
"content_id": "logo"
}
]
}
JSON
import { readFile } from "node:fs/promises";
import { MailChannels } from "mailchannels-sdk";
const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL, LOGO_PATH } = process.env;
if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running");
if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)");
if (!TO_EMAIL) throw new Error("Set TO_EMAIL");
if (!LOGO_PATH) throw new Error("Set LOGO_PATH to the image you want to embed, e.g. ./logo.png");
const logoBytes = await readFile(LOGO_PATH);
const mailchannels = new MailChannels(MAILCHANNELS_API_KEY);
// Setting content_id is what embeds the attachment inline: it's referenced from the HTML
// body via the matching cid: URI, instead of showing up as a downloadable attachment.
const { data, error } = await mailchannels.emails.send({
from: { email: FROM_EMAIL, name: "Your Name" },
to: { email: TO_EMAIL, name: "Recipient" },
subject: "Hello from MailChannels",
html: `<p>Hello! Here's our logo: <img src="cid:logo"></p>`,
attachments: [
{
type: "image/png",
filename: "logo.png",
content: logoBytes.toString("base64"),
content_id: "logo",
},
],
});
if (error) {
console.error("Send failed:", error);
process.exit(1);
}
console.log(data);
import base64
import os
import sys
import mailchannels
if not os.environ.get("MAILCHANNELS_API_KEY"):
sys.exit("Set MAILCHANNELS_API_KEY before running")
from_email = os.environ.get("FROM_EMAIL")
to_email = os.environ.get("TO_EMAIL")
logo_path = os.environ.get("LOGO_PATH")
if not from_email:
sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)")
if not to_email:
sys.exit("Set TO_EMAIL")
if not logo_path:
sys.exit("Set LOGO_PATH to the image you want to embed, e.g. ./logo.png")
with open(logo_path, "rb") as f:
logo_base64 = base64.b64encode(f.read()).decode("ascii")
# Setting content_id is what embeds the attachment inline: it's referenced from the HTML
# body via the matching cid: URI, instead of showing up as a downloadable attachment.
response = mailchannels.Emails.send(
{
"from": {"email": from_email, "name": "Your Name"},
"to": [{"email": to_email, "name": "Recipient"}],
"subject": "Hello from MailChannels",
"html": '<p>Hello! Here\'s our logo: <img src="cid:logo"></p>',
"attachments": [
{
"type": "image/png",
"filename": "logo.png",
"content": logo_base64,
"content_id": "logo",
}
],
}
)
print(response)
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MailChannels\Client;
$api_key = getenv('MAILCHANNELS_API_KEY') or exit("Error: MAILCHANNELS_API_KEY is not set\n");
$from_email = getenv('FROM_EMAIL') or exit("Error: FROM_EMAIL is not set\n");
$to_email = getenv('TO_EMAIL') or exit("Error: TO_EMAIL is not set\n");
$logo_path = getenv('LOGO_PATH') or exit("Error: LOGO_PATH is not set\n");
$client = new Client(apiKey: $api_key);
$logo = file_get_contents($logo_path);
if ($logo === false) {
exit("Error: could not read file: {$logo_path}\n");
}
// Setting content_id is what embeds the attachment inline: it's referenced from the HTML
// body via the matching cid: URI, instead of showing up as a downloadable attachment.
$response = $client->emails->send([
'from' => ['email' => $from_email, 'name' => 'Your Name'],
'to' => ['email' => $to_email, 'name' => 'Recipient'],
'subject' => 'Hello from MailChannels',
'html' => '<p>Hello! Here\'s our logo: <img src="cid:logo"></p>',
'attachments' => [
[
'content' => base64_encode($logo),
'filename' => basename($logo_path),
'type' => mime_content_type($logo_path) ?: 'application/octet-stream',
'content_id' => 'logo',
],
],
]);
print_r($response->toArray());
content_id must be unique across all attachments in the request.Never send an attachment that you received from a user without first scanning it for viruses and malware.

