Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt

Use this file to discover all available pages before exploring further.

import { imessage } from "spectrum-ts/providers/imessage";
The iMessage provider supports three connection modes — local, cloud, and dedicated — and exposes iMessage-specific features (tapbacks, DM vs group spaces, per-phone routing) through platform narrowing.

Connection modes

Authenticates with Spectrum Cloud and connects to managed iMessage infrastructure via gRPC. Full feature set: send, receive, typing, reactions, replies, and group creation.
imessage.config();
Tokens are renewed automatically at 80% of their TTL. Requires projectId and projectSecret on the Spectrum() call:
const app = await Spectrum({
  projectId: process.env.PROJECT_ID!,
  projectSecret: process.env.PROJECT_SECRET!,
  providers: [imessage.config()],
});

Line model

Cloud mode routes your messages through phone numbers (“lines”) provisioned by Spectrum. Which lines you get depends on your plan, and the difference is mostly invisible to end users.
PlanLine allocationWhat end users see
Free / ProShared pool. Each of your end users is routed through a different number from a shared pool.A normal iMessage from a number that may differ across recipients.
BusinessDedicated. All of your end users text the same number, which belongs to your project.A normal iMessage, always from the same number.
End-user delivery is identical in both modes; the distinction is which number sends.

Auto-scale

When traffic to a dedicated line approaches its per-line capacity, Spectrum can automatically provision an additional line so deliverability isn’t affected. Auto-scale is an opt-in feature on the Business plan. Enable it in your project settings if you’d rather not get paged when a line saturates.
These are managed Spectrum Cloud features. If you’re on the open-source path (imessage.config({ local: true }) or your own dedicated relay), you provide your own iCloud account and managed-line concepts don’t apply.

Quotas

Default per-server and per-line quotas apply. Contact help@photon.codes for an increase.
  • 5,000 messages per server per day. Counts every message your instance sends across all chats. Additional sends are rejected until the window resets.
  • 50 new conversations initiated per line per day. A “new conversation” is the first message your line sends to a recipient it has never messaged before. Replies within existing conversations don’t count.

Space types

iMessage spaces carry a type field — "dm" or "group" — and a phone field indicating which phone number the conversation is routed through. Both are accessible through narrowing:
for await (const [space, message] of app.messages) {
  if (message.platform !== "iMessage") continue;
  const im = imessage(space);
  console.log(im.phone); // the phone number handling this conversation
  if (im.type === "group") {
    // group chat logic
  }
}

Creating conversations

Resolve users by phone number or email, then pass them to space():
const im = imessage(app);
const alice = await im.user("+15551111111");
const bob = await im.user("+15552222222");

// DM
const dm = await im.space(alice);
await dm.send("Hi Alice");

// Group
const group = await im.space(alice, bob);
await group.send("Welcome to the group.");
Space creation requires cloud or dedicated mode. In local mode space() throws — the local Messages database doesn’t expose chat creation.

Per-phone routing

If your account has multiple dedicated phone numbers, you can pin a conversation to a specific line by passing phone as a space parameter:
const dm = await im.space(alice, { phone: "+15559999999" });
When omitted, Spectrum picks a phone at random from the available dedicated lines. All subsequent actions on that space — sending, typing, replies, edits, reactions, and lookups — route through the chosen number.
Per-phone routing applies to dedicated lines (Business plan) only. On shared-pool plans the phone parameter is ignored — all conversations route through the shared pool automatically.

Message effects

iMessage supports bubble effects (sent message animation) and screen effects (full-screen animation on receive). Wrap any content with effect():
import { effect, imessage } from "spectrum-ts/providers/imessage";

await space.send(effect("Happy birthday!", imessage.effect.message.celebration));
await space.send(effect(attachment("/path/to/photo.jpg"), imessage.effect.message.confetti));
The wrapped content can be a string or any attachment(...). Effects only apply on iMessage — other platforms see the inner content unchanged.
ConstantValue
imessage.effect.message.slam"com.apple.MobileSMS.expressivesend.impact"
imessage.effect.message.loud"com.apple.MobileSMS.expressivesend.loud"
imessage.effect.message.gentle"com.apple.MobileSMS.expressivesend.gentle"
imessage.effect.message.invisible"com.apple.MobileSMS.expressivesend.invisibleink"
ConstantValue
imessage.effect.message.confetti"com.apple.messages.effect.CKConfettiEffect"
imessage.effect.message.fireworks"com.apple.messages.effect.CKFireworksEffect"
imessage.effect.message.balloons"com.apple.messages.effect.CKBalloonEffect"
imessage.effect.message.heart"com.apple.messages.effect.CKHeartEffect"
imessage.effect.message.lasers"com.apple.messages.effect.CKLasersEffect"
imessage.effect.message.celebration"com.apple.messages.effect.CKHappyBirthdayEffect"
imessage.effect.message.sparkles"com.apple.messages.effect.CKSparklesEffect"
imessage.effect.message.spotlight"com.apple.messages.effect.CKSpotlightEffect"
imessage.effect.message.echo"com.apple.messages.effect.CKEchoEffect"

Chat backgrounds

Set or clear the chat background image. Import background from the iMessage provider and use the sugar method on a narrowed space:
import { background, imessage } from "spectrum-ts/providers/imessage";

const im = imessage(space);

// Set from a file path — MIME type inferred from the extension
await im.background("./wallpaper.jpg");

// Set from a buffer — mimeType is required
await im.background(buffer, { mimeType: "image/jpeg" });

// Clear the current background
await im.background("clear");
space.background(...) is sugar for space.send(background(...)). The canonical form works on any space reference:
await space.send(background("./wallpaper.jpg"));
await space.send(background("clear"));
After a successful set, the background usually syncs to other users’ devices within 30s. The background asset is uploaded to iCloud and then distributed to the other members of the conversation. Display time is not a hard SLA: network state, iCloud state, and the Messages client state can all affect when the UI appears.
StageWhat happens
Before background(...) resolvesThe provider waits until the background asset reaches a distributable state
After background(...) resolvesThe conversation has accepted the background change; iCloud distributes the asset to other members
Other members’ devicesThe background appears after the device receives the iCloud distribution
Background UI may not appear in these cases:
CaseResult
The recipient’s network, iCloud, or Messages state is unhealthyThe background may appear late, often after reopening Messages
A group member has never spoken, interacted, or is treated by the system as unknown (untrusted)Apple may not show the background UI to that member
The second case is an Apple Messages display limit, not a Spectrum option. If one group member never sees the background, have that member send a message in the group, mark the sender as known, or reopen Messages before retrying the background change.
Chat backgrounds require cloud or dedicated mode. In local mode, background() throws an UnsupportedError.
The string "clear" is a reserved sentinel. If you have a file literally named clear with no extension, pass "./clear" or load it as a Buffer.

Tapback constants

iMessage uses a fixed set of tapback reactions. The imessage object exposes them as constants:
ConstantValue
imessage.tapbacks.love"love"
imessage.tapbacks.like"like"
imessage.tapbacks.dislike"dislike"
imessage.tapbacks.laugh"laugh"
imessage.tapbacks.emphasize"emphasize"
imessage.tapbacks.question"question"
import { imessage } from "spectrum-ts/providers/imessage";

await message.react(imessage.tapbacks.laugh);
See Reactions and replies for the cross-platform reaction model.