im.attachments handles file attachments - uploading before sending, streaming downloads, transparent iCloud recovery, and extracting Live Photos.
Supported file types
| Category | Formats | Notes |
|---|
| Images | HEIC, HEIF | Native Apple format used by iPhone cameras |
| Video | MOV, MP4 | MOV for Live Photos, MP4 for general video |
| Audio | M4A, CAF, MP3 | M4A for voice memos and audio messages |
| Documents | PDF | |
| Stickers | PNG, GIF, APNG | Animated stickers use APNG |
iMessage stores and transfers images in HEIC/HEIF. If you’re uploading a JPEG
or PNG, the server will accept it, but images received from iMessage will
arrive as HEIC/HEIF. Convert them before displaying in non-Apple contexts.
Upload an attachment
Upload a file and get back an AttachmentInfo object. Use the guid property to pass to im.messages.send().
const info = await im.attachments.upload({
path: "/path/to/photo.jpg",
fileName: "photo.jpg", // optional display name
mimeType: "image/jpeg", // optional override
});
await im.messages.send(chat, "", { attachment: info.guid });
AttachmentInput is a union type with two variants - file path or raw bytes:
type AttachmentInput =
| { data: Uint8Array; fileName: string; mimeType: string }
| { path: string; fileName?: string; mimeType?: string };
Upload a Live Photo
Upload a paired HEIC image + MOV video as a Live Photo:
const info = await im.attachments.uploadLivePhoto({
image: {
data: imageBytes, // Uint8Array
fileName: "photo.heic",
mimeType: "image/heic",
},
video: {
data: videoBytes, // Uint8Array
},
});
const info = await im.attachments.get(attachmentGuid);
interface AttachmentInfo {
guid: AttachmentGuid;
originalGuid?: AttachmentGuid;
mimeType: string;
fileName: string;
totalBytes: number;
transferState: TransferState;
hasLivePhoto: boolean;
height?: number;
hideAttachment: boolean;
isOutgoing: boolean;
isSticker: boolean;
uti: string;
width?: number;
_raw?: unknown;
}
Count attachments
const total = await im.attachments.count();
Download an attachment
download() returns a StreamedDownload synchronously - both a streaming and a buffered consumption path.
const dl = im.attachments.download(attachmentGuid);
dl.totalBytes; // file size, known from the first chunk
// Stream it
for await (const chunk of dl.stream) {
// ReadableStream<Uint8Array>
process(chunk);
}
// Or buffer the whole thing
const buffer = await dl.arrayBuffer(); // Uint8Array
interface StreamedDownload {
totalBytes: number;
stream: ReadableStream<Uint8Array>;
arrayBuffer(): Promise<Uint8Array>;
}
Quick buffer download
A convenience method that combines download() and arrayBuffer():
const bytes = await im.attachments.downloadBuffer(attachmentGuid);
// bytes: Uint8Array
Live Photos
Extract the video component from a Live Photo attachment:
const videoDl = im.attachments.getLivePhoto(attachmentGuid);
const videoBytes = await videoDl.arrayBuffer();
getLivePhoto() returns a StreamedDownload synchronously, just like download().