Skip to main content
im.attachments handles file attachments - uploading before sending, streaming downloads, transparent iCloud recovery, and extracting Live Photos.

Supported file types

CategoryFormatsNotes
ImagesHEIC, HEIFNative Apple format used by iPhone cameras
VideoMOV, MP4MOV for Live Photos, MP4 for general video
AudioM4A, CAF, MP3M4A for voice memos and audio messages
DocumentsPDF
StickersPNG, GIF, APNGAnimated 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
  },
});

Get attachment metadata

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().