Skip to main content

Overview

Create interactive polls in iMessage conversations and programmatically vote on them. Polls are a native iMessage feature that work across all Apple devices.

Creating a Poll

Create a poll with multiple choice options:
import { createSDK, handleError } from "./utils";

const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890";

async function main() {
    const sdk = createSDK();

    sdk.on("ready", async () => {
        console.log("Poll creation example...\n");

        try {
            console.log("Creating a poll...");
            const pollMessage = await sdk.polls.create({
                chatGuid: CHAT_GUID,
                title: "",
                options: [
                    "Option A - First choice",
                    "Option B - Second choice",
                    "Option C - Third choice",
                    "Option D - Fourth choice",
                ],
            });

            console.log("\n✓ Poll created successfully!");
            console.log(`Poll message GUID: ${pollMessage.guid}`);
            console.log(`Balloon Bundle ID: ${pollMessage.balloonBundleId}`);

            if (pollMessage.payloadData) {
                console.log(`\nPayload Data: ${JSON.stringify(pollMessage.payloadData, null, 2)}`);
            }
        } catch (error) {
            handleError(error, "Failed to create poll");
        }

        await sdk.close();
        process.exit(0);
    });

    await sdk.connect();
}

main().catch(console.error);

Poll Parameters

chatGuid
string
required
The GUID of the chat where the poll will be created
title
string
Optional poll question/title. Can be an empty string.
options
string[]
required
Array of poll options (minimum 2, maximum typically 10)

Voting on Polls

Vote on an existing poll by selecting an option:
import { parsePollDefinition } from "../lib/poll-utils";
import { createSDK, handleError } from "./utils";

const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890";
const POLL_MESSAGE_GUID = process.env.POLL_MESSAGE_GUID;

async function main() {
    const sdk = createSDK();

    sdk.on("ready", async () => {
        try {
            let pollMessageGuid = POLL_MESSAGE_GUID;
            let optionIdentifier: string | undefined;

            // Create a poll if we don't have a GUID
            if (!pollMessageGuid) {
                const poll = await sdk.polls.create({
                    chatGuid: CHAT_GUID,
                    title: "Vote test",
                    options: ["Option A", "Option B", "Option C"],
                });

                pollMessageGuid = poll.guid;
                console.log(`created poll: ${pollMessageGuid}`);

                const pollData = parsePollDefinition(poll);
                optionIdentifier = pollData?.options?.[0]?.optionIdentifier;
                console.log(`options: ${pollData?.options?.map((o) => o.text).join(", ")}`);

                await new Promise((resolve) => setTimeout(resolve, 2000));
            }

            // Get poll details if needed
            if (!optionIdentifier) {
                const poll = await sdk.messages.getMessage(pollMessageGuid!, { with: ["payloadData"] });
                const pollData = parsePollDefinition(poll);
                optionIdentifier = pollData?.options?.[0]?.optionIdentifier;
            }

            if (!optionIdentifier) {
                throw new Error("Could not find option identifier");
            }

            // Cast vote
            const vote = await sdk.polls.vote({
                chatGuid: CHAT_GUID,
                pollMessageGuid: pollMessageGuid!,
                optionIdentifier,
            });

            console.log(`voted: ${vote.guid}`);
        } catch (error) {
            handleError(error, "Failed to vote");
        }

        await sdk.close();
        process.exit(0);
    });

    await sdk.connect();
}

main().catch(console.error);

Vote Parameters

chatGuid
string
required
The GUID of the chat containing the poll
pollMessageGuid
string
required
The message GUID of the poll
optionIdentifier
string
required
The unique identifier for the option you’re voting for

Complete Example: Create and Vote

const sdk = createSDK();

sdk.on("ready", async () => {
    // 1. Create poll
    const poll = await sdk.polls.create({
        chatGuid: CHAT_GUID,
        title: "What's for lunch?",
        options: ["Pizza", "Burgers", "Salad", "Sushi"],
    });
    console.log(`Poll created: ${poll.guid}`);

    // 2. Parse poll data
    const pollData = parsePollDefinition(poll);
    console.log("Options:");
    pollData?.options?.forEach((opt, i) => {
        console.log(`  ${i + 1}. ${opt.text}`);
    });

    // 3. Vote for first option
    const firstOption = pollData?.options?.[0];
    if (firstOption) {
        await sdk.polls.vote({
            chatGuid: CHAT_GUID,
            pollMessageGuid: poll.guid,
            optionIdentifier: firstOption.optionIdentifier,
        });
        console.log(`Voted for: ${firstOption.text}`);
    }

    await sdk.close();
});

await sdk.connect();

Listening for Poll Messages

Detect and handle incoming polls:
import { isPollMessage, getPollSummary } from "../lib/poll-utils";

sdk.on("new-message", (message) => {
    const sender = message.handle?.address ?? "unknown";

    if (isPollMessage(message)) {
        const pollSummary = getPollSummary(message);
        console.log(`\n${sender} created a poll:`);
        console.log(pollSummary);
    } else {
        console.log(`\n${sender}: ${message.text ?? "(no text)"}`);
    }
});

Poll Utils

The SDK includes utility functions for working with polls:

isPollMessage

import { isPollMessage } from "../lib/poll-utils";

if (isPollMessage(message)) {
    console.log("This is a poll!");
}

getPollSummary

import { getPollSummary } from "../lib/poll-utils";

const summary = getPollSummary(message);
console.log(summary); // Human-readable poll description

parsePollDefinition

import { parsePollDefinition } from "../lib/poll-utils";

const pollData = parsePollDefinition(message);
if (pollData) {
    console.log(`Title: ${pollData.title}`);
    pollData.options?.forEach(opt => {
        console.log(`- ${opt.text} (ID: ${opt.optionIdentifier})`);
    });
}

Advanced Examples

Auto-Voter Bot

sdk.on("new-message", async (message) => {
    if (!isPollMessage(message)) return;
    
    const chat = message.chats?.[0];
    if (!chat) return;

    const pollData = parsePollDefinition(message);
    if (!pollData?.options?.length) return;

    // Vote for first option automatically
    const firstOption = pollData.options[0];
    
    await sdk.polls.vote({
        chatGuid: chat.guid,
        pollMessageGuid: message.guid,
        optionIdentifier: firstOption.optionIdentifier,
    });

    console.log(`Auto-voted for: ${firstOption.text}`);
});

Poll Analytics

const pollStats = new Map();

sdk.on("new-message", (message) => {
    if (!isPollMessage(message)) return;
    
    const pollData = parsePollDefinition(message);
    if (!pollData) return;

    pollStats.set(message.guid, {
        title: pollData.title,
        optionCount: pollData.options?.length || 0,
        created: message.dateCreated,
        creator: message.handle?.address,
    });

    console.log(`\nPoll Statistics:`);
    console.log(`  Total polls: ${pollStats.size}`);
});

Smart Voting

sdk.on("new-message", async (message) => {
    if (!isPollMessage(message)) return;
    
    const chat = message.chats?.[0];
    if (!chat) return;

    const pollData = parsePollDefinition(message);
    if (!pollData?.options) return;

    // Vote based on keywords in options
    const preferredOption = pollData.options.find(opt => 
        opt.text.toLowerCase().includes("pizza") ||
        opt.text.toLowerCase().includes("yes")
    );

    if (preferredOption) {
        await sdk.polls.vote({
            chatGuid: chat.guid,
            pollMessageGuid: message.guid,
            optionIdentifier: preferredOption.optionIdentifier,
        });
        console.log(`Smart-voted for: ${preferredOption.text}`);
    }
});

Poll Best Practices

Poll options should be short and clear:
// Good
options: ["Yes", "No", "Maybe"]

// Too long
options: [
    "Yes, I definitely want to attend the meeting",
    "No, I cannot make it to the meeting",
]
Too many options make polls hard to read:
// Good: 2-6 options
options: ["Mon", "Tue", "Wed", "Thu", "Fri"]

// Too many: > 10 options
Give the poll time to render before voting:
const poll = await sdk.polls.create({...});
await new Promise(r => setTimeout(r, 2000)); // Wait 2 seconds
await sdk.polls.vote({...});
You can only vote once per poll. Voting again will change your vote to the new option.
Store poll GUIDs and option identifiers in a database if you need to reference them later.

Changing Your Vote

To change your vote, simply vote again with a different option:
// Initial vote
await sdk.polls.vote({
    chatGuid: CHAT_GUID,
    pollMessageGuid: pollGuid,
    optionIdentifier: option1,
});

// Change vote
await sdk.polls.vote({
    chatGuid: CHAT_GUID,
    pollMessageGuid: pollGuid,
    optionIdentifier: option2, // Different option
});

Troubleshooting

Common issues:
  1. “Could not find option identifier” - Make sure to fetch the poll with payloadData:
    const poll = await sdk.messages.getMessage(guid, { with: ["payloadData"] });
    
  2. Poll not displaying - Ensure recipient is using a compatible iOS version (iOS 14+)
  3. Vote not registering - Wait a moment after creating a poll before voting

Next Steps

Rich Links

Send link previews

Group Chats

Create polls in groups

Build docs developers (and LLMs) love