Skip to main content
Use this page to choose an iMessage connection mode and understand how Spectrum routes iMessage conversations.

Connection modes

Authenticates with Spectrum Cloud and connects to managed iMessage infrastructure via gRPC. Supports sending, receiving, typing indicators, reactions, and replies. Group creation and inbound group-change events require a dedicated line.
imessage.config();
With automatic discovery, tokens are renewed at 80% of their TTL. This 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()],
});
Spectrum discovers all cloud lines owned by the project and renews their tokens automatically. For advanced routing, you can instead provide a subset of the project’s cloud clients:
imessage.config({
  clients: [
    { address: "line-1.imsg.photon.codes:443", token: "your-token", phone: "+15551111111" },
  ],
});
Explicit clients let you control which cloud-owned lines this SDK instance subscribes to. They are still cloud mode. Their tokens are not renewed by the SDK, so you are responsible for keeping them current; most applications should use automatic discovery.

Line model

Cloud mode routes your messages through phone numbers, also called 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 seeGroup support
Free / ProShared pool. Each end user is routed through a number from a shared pool.A normal iMessage from a number that may differ across recipients.No group creation or inbound group-change events.
BusinessDedicated. All end users text the same number, which belongs to your project.A normal iMessage, always from the same number.Group creation and inbound group-change events are supported.
DM delivery is identical across both cloud line models. The developer-facing differences are sender-number allocation and group support.
Shared-pool mode does not create group chats and does not subscribe to iMessage’s group-event stream. Changes such as adding or removing a member, leaving, renaming the chat, or changing its avatar will not appear on app.messages. Use a Business dedicated line when your integration depends on group workflows.

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 Spectrum Cloud features. In local mode (imessage.config({ local: true })), you provide your own iCloud account and managed-line concepts do not apply.

Quotas

Default per-server and per-line quotas apply. Contact [email protected] 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, either "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
  }
}

User properties

iMessage users carry optional platform-specific fields when resolved through narrowing. These are available when the platform has sender details for the user:
FieldTypeDescription
addressstring (optional)The user’s phone number or email address.
countrystring (optional)The user’s country code.
service"iMessage" | "SMS" | "RCS" | "unknown" (optional)The messaging service the user is reachable on.
const im = imessage(app);
const alice = await im.user("+15551111111");
console.log(alice.address, alice.country, alice.service);
These fields are also available on message.sender after narrowing:
for await (const [space, message] of app.messages) {
  if (message.platform !== "iMessage") continue;
  const imMsg = imessage(message);
  console.log(imMsg.sender.service);
}

Creating conversations

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

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

// Group (dedicated lines only)
const group = await im.space.create([alice, bob]);
await group.send("Welcome to the group.");
To look up an existing conversation by its chat GUID, use space.get(id):
const existing = await im.space.get("any;-;+15551111111");
DM creation works in cloud mode. Local mode can construct a deterministic DM reference, but it cannot create a group because the local Messages database does not expose chat creation. Group creation requires a dedicated line. In shared-pool mode, passing multiple users to space.create() throws an UnsupportedError. You can use space.get(chatGuid) to reference an existing group, but shared mode still does not receive membership or metadata changes from the group-event stream.

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.create(alice, { phone: "+15559999999" });
When omitted, Spectrum picks a phone at random from the available dedicated lines. All subsequent actions on that space route through the chosen number, including sending, typing, replies, edits, reactions, unsends, and lookups.
Per-phone routing applies to dedicated lines on the Business plan only. On shared-pool plans the phone parameter is ignored because all conversations route through the shared pool automatically.