Frequently Asked Questions

Short answers. Flip to dev mode if you want the technical version.

What is Pollis?

A messaging app with group channels and direct messages, like Slack, for Mac, Windows, Linux and Android, with iOS on the way. Your messages are encrypted on your device before they leave it, so the people running Pollis can't read them.

Tauri desktop client with a React UI, an Expo / React Native mobile app, and a terminal client, all on one Rust core (pollis-core; mobile reaches it through uniffi) for cryptography and MLS group state. Every database read and write goes through the Delivery Service (an in-repo axum server); the client holds no database credential, and pollis-core links no remote database driver, only SQLCipher for its local store. Attachment bytes go straight to R2 on presigned URLs the Delivery Service issues. Messages are end-to-end encrypted with MLS (RFC 9420) via openmls. The database and Delivery Service see ciphertext envelopes and routing metadata, never plaintext.

How is it different from Slack, Signal, or Discord?

Slack and Discord can read your messages. Signal can't read yours either, but it's phone-number based and built for personal chats. Pollis is built for group workspaces like Slack, but with Signal-style privacy.

Slack/Discord: plaintext server storage. Signal: Double Ratchet, phone-number identity, optimized for 1:1 + small groups. Pollis: MLS-based group ratchet, username + email identity, Slack-style multi-channel workspaces, native desktop and mobile apps plus a terminal client.

Is it really end-to-end encrypted?

Yes, for what you say. Messages and calls are encrypted on your device, and your private keys never leave your devices unencrypted.

What the server can still see: your email and username, which groups and DMs you're in and what they're called, and when each message was sent and to which conversation. Stored messages don't record who sent them, but at the moment you hit send the server does know it's you (see the next question). Emoji reactions, avatars and group icons are not encrypted.

Client-side MLS encryption (RFC 9420) via openmls. On desktop, private keys live in the OS keystore (macOS Keychain / Windows Credential Manager / Linux Secret Service) via the keyring crate. On mobile they sit in an encrypted file whose key is held by the iOS Keychain (this-device-only) or a non-exportable Android Keystore key. Where no keystore is available, they go in an encrypted file bound to the machine, never in plaintext. They are never sent anywhere.

Voice: audio frames are AES-GCM encrypted by libwebrtc's FrameCryptor after Opus and before SRTP, keyed by a 32-byte secret from MlsGroup::export_secret("pollis/voice/v1", epoch, 32) on the channel's MLS group. The LiveKit SFU forwards ciphertext only, and the key rotates with every MLS epoch.

Voice and video are desktop-only. Server sees: email and username; group and channel names, descriptions and membership; full-precision message timestamps and per-conversation sequence numbers; padded ciphertext sizes; the sending account and device at submit time (every request carries signed X-Pollis-User / X-Pollis-Device headers); reactions as (message_id, user_id, emoji); avatars and group icons; public keys and device registrations; IP addresses, used for in-memory rate limiting and not logged. The stored message row does not record its sender (sealed sender). LiveKit sees per-room HMAC pseudonyms, not account IDs or room names. Server does not see: message text, voice or video, private keys.

Can the server tell who sent a message?

Not from what it stores. The stored message does not say who sent it; only people in the conversation can tell, from the sealed contents. A database breach or a legal demand for stored data can't reveal who said what.

The caveat: when you hit send, the delivery server checks that you're allowed to post in that conversation, so at that moment it does see it's you. Removing that is future work.

Sealed sender (shipped). MLS already authenticates the sender inside the ciphertext via the message's BasicCredential ({user_id}:{device_id}), so the plaintext sender_id the server used to store was redundant. The stored envelope is now sealed (migration 000008_message_envelope_sealed_sender.sql), and the recipient attributes the message from the MLS credential on decrypt.

This covers data at rest only. The Delivery Service still authenticates the send with an X-Pollis-User header, so it sees the sender at send time. Anonymous membership proofs, which would remove that, are not shipped.

Does the length of my messages give anything away?

Very little. Every text message is padded before it's sealed: short messages all come out the same size, and longer ones are rounded up, so the server only learns a rough size range. Attachments are not padded.

Text plaintext is padded before MLS encryption (pollis-core/src/commands/messages/framing.rs): everything up to 256 bytes collapses to one size, and above that lengths are rounded up with PADMÉ (worst-case overhead about 12%). Redactions, read receipts and thread replies are padded the same way. Attachments are not padded: their ciphertext is the file size plus a 16-byte tag per 4 MiB chunk.

Do notifications leak who's messaging me?

No. The real-time "new message" ping that tells your app to fetch doesn't say who sent it. Your device learns the sender only when it opens the sealed message.

The realtime new_message payload carries no sender_id; it is a wake-up ping, and the recipient takes the sender from the decrypted MLS envelope.

What happens if someone's cryptographic key changes?

Pollis warns you. On desktop, a red shield appears next to that person's name in your DM list and in group member lists, and a notice appears in your DM with them so you can re-verify them another way, in person or by phone.

You verify a person once, and the green shield follows them across your account. If their key changes, all those shields turn red together. On mobile, you compare and verify safety numbers from the person's profile.

Signal-style 60-digit safety numbers derived from both parties' account_id_pub (SHA-512^5200, see pollis-core/src/commands/safety.rs). First-seen keys are pinned silently (TOFU) and surfaced on mismatch via a KeyChanged realtime event. Pinning runs on every DM ingest and on every group-reconcile commit (batch_check_and_pin_account_keys), so a server-side key swap is caught in any conversation type. Verification state lives in contact_verification, keyed by peer_user_id rather than per conversation, which is why the shield follows the contact. UI surfaces: desktop shows shields in the DM list and member lists and the KeyChangeBanner in DMs; mobile shows the safety number and verify action on the user profile only.

What happens to my messages if I get a new device or reset my account?

You will likely lose your old messages, on purpose.

A new device only sees messages sent after it's added, and resetting your account wipes prior messages. We don't back up your history to the cloud, because a backup would be a target for attackers and subpoenas.

If you need a permanent archive of everything you've ever said, Pollis isn't the right tool.

A new device joining an MLS group gets keys for the current epoch only; there is no historical backfill. Identity reset wipes local message storage. There is no encrypted cloud backup (no Megolm-style key backup), and none is planned: no key escrow, smaller attack surface.

Which platforms are supported?

macOS, Windows, Linux and Android. iOS is in testing and not on the App Store yet. There is also a terminal client for macOS, Linux and Windows.

macOS (Apple Silicon), Windows x64, Linux (.deb, .rpm, .AppImage, AUR). Binaries are code-signed: Apple Developer ID (notarized) on macOS, Azure Trusted Signing on Windows. Android: a sideload APK per ABI (arm64-v8a, armeabi-v7a, x86_64) on pollis.com/android, signed with a pinned key. Terminal client (pollis): Linux, macOS (Apple Silicon), Windows.

Is there an iOS or Android app?

Android, yes: download it from pollis.com/android. A Google Play listing is in review. The iOS app is in testing and not on the App Store yet.

A React Native / Expo app in mobile/ on the same Rust core (pollis-core) through uniffi bindings. Android ships as a signed sideload APK (mobile-apk-release.yml, certificate SHA-256 pinned on the Android page); the Play Store build is in review and is re-signed by Google, so Play and sideload installs can't update each other. iOS is on TestFlight, not yet on the App Store. Mobile has no voice or video (by design) and is not yet in the binary transparency log.

Is Pollis open source?

Not strictly. It is source-available: the whole source is public on GitHub, and you can read it, build it, and check our work, which is what the verification claims on this site rely on. The licence permits personal and noncommercial use only. Using Pollis inside a business needs a separate commercial licence from us.

No. It is source-available, not OSI open source.

The repo at github.com/pollis-app/pollis is published under the PolyForm Noncommercial License 1.0.0. Any noncommercial purpose is permitted, including personal use, hobby projects, academic and public research, and use by charities, schools and government institutions. For those purposes you may run, modify, and redistribute it, provided you pass the licence terms along. Any commercial or business use requires a separate commercial licence; contact dankral01@gmail.com. The canonical text is LICENSE.md in the repo.

The licence does not restrict auditing: reading the source, rebuilding a release to compare against the transparency log, or building and running pollis-verify yourself.

How do I know the app I downloaded hasn't been tampered with?

Pollis publishes a fingerprint of every release to a public, tamper-evident log. A small tool, pollis-verify, lets anyone confirm that the app they're running is one Pollis published. A tampered build given to one person would either appear in the public log permanently or be missing from it, and either is detectable.

On Linux, an automated rebuild from the public source after every release also confirms the released app matches byte for byte. macOS and Windows don't have that step yet, so there the log proves the app matches what Pollis published, not that it was built from the public source.

Binary transparency (shipped). Each released artifact's hashes are appended to an append-only log at verify.pollis.com/v1/binaries, anchored to a single pinned log key (post-quantum ML-DSA-44, published on the artifacts page). pollis-verify release <tag> checks a build against the log, trusting only that key.

Each leaf also has a toolchain field meant to record the build recipe. No workflow ever set it, so all 258 leaves through v1.9.7 carry "rustc": "unknown". The log is append-only, so they cannot be corrected: their hashes are right, their recipe field is not. The release workflow now fails rather than record unknown, so leaves from later releases carry a real recipe.

Each release from then on also publishes a CycloneDX SBOM (pollis-<tag>.cdx.json) listing every Rust and JavaScript dependency the build resolved, plus a build-recipe sidecar per platform. The SBOM is itself a provenance subject, so its digest is anchored in Sigstore's public log and a swapped SBOM would be detectable.

Also shipped:

  • a post-release workflow that rebuilds the Linux AppImage from public source and fails on any hash mismatch with the log;
  • SLSA build provenance and keyless cosign signatures on every desktop artifact for all three platforms, anchored in Sigstore's Rekor log, so verification needs no Pollis-held key;
  • an in-app "verify this build" button on the security screen, which hashes the running binary and runs the same release check as the CLI.

Limits: the rebuild covers the Linux AppImage only (not .deb/.rpm, macOS, or Windows), and it needs the published build recipe as well as the source, because a build-time value is baked in. Every release's verdict is recorded on the assurance page, misses included: red runs from a since-fixed reporting bug, releases left inconclusive because the rebuild ran on a different CI machine image than the release, and a few early releases that were never checked. Provenance is not a release gate, and two earlier releases shipped without it. The in-app check is advisory: it proves log inclusion, not reproduction from source, and reports pending until the log's next daily rebuild covers a new release.

Is it free?

For personal and other noncommercial use, yes, with no paid tier. Using it inside a business needs a separate commercial licence.

Free for any noncommercial purpose under PolyForm Noncommercial 1.0.0. Commercial or business use requires a separate commercial licence (see the licensing question above).

Why MLS instead of Signal Protocol?

Pollis is built around group chats. MLS is a newer standard designed for groups; Signal's protocol is built for 1:1 and gets expensive in large groups.

MLS (RFC 9420) gives a group a single shared key schedule with O(log n) membership changes. Signal groups build on pairwise Double Ratchet sessions (sender keys distributed pairwise), so adding or removing a member costs O(N) pairwise messages per sender. For Slack-style channels with many members, MLS scales better.

Do you store my email address?

Yes, we need it to send you sign-in codes. No phone number required.

Yes. Email OTP is the auth primitive, so the email is stored server-side to deliver the OTP and bind it to the account. There are no server-side sessions: every request is signed with the device's ML-DSA-44 key, which is what the keystore holds. A short-lived one-time-code token is used only while enrolling a device.

How do I create or join a group?

Anyone can create a group inside the app. To add someone, invite them by their Pollis username or send them an invite link; people can also ask to join, and an admin approves. Everyone needs a Pollis account. Direct messages start as a request the other person accepts, and can include more than two people.

Groups are created via the create_group backend command; members are added by username via send_group_invite, by invite link (create_group_invite_link / redeem_group_invite_link), or by an approved join request (request_group_access / approve_join_request). Adding someone generates an MLS Welcome that seeds their device into the group ratchet at the current epoch. Each DM is its own MLS group: one per pair for 1:1, with 3+ member DMs supported (add_user_to_dm_channel), gated by accept_dm_request.

What happens if the server goes down?

You can't send or receive messages until it's back. The app still opens and on-device search still works, but browsing your conversations needs the server today. An outage doesn't affect privacy.

The server is required for message delivery, MLS commit routing, and R2 uploads. Unlock and on-device search work offline against the local encrypted store, but the group list and message pages call the Delivery Service before reading locally (ds_reads::bootstrap, catch_up), so they fail during an outage instead of falling back to local data. Because the server can't decrypt, downtime has no confidentiality impact.

Where do my files go when I attach something?

Files are encrypted on your device before upload. The storage provider sees only encrypted bytes, and only people in the group can decrypt them.

Attachments are encrypted client-side and uploaded to Cloudflare R2. The decryption key travels inside the MLS-encrypted message, so R2 only ever holds ciphertext. Recipients fetch by key and decrypt locally.

Should I trust Pollis with my life?

Pollis is in open preview and has not been independently audited. If your physical safety depends on unbreakable communication, use a mature, audited tool built for that threat model.

Open preview status. No independent human audit yet; there have been two full adversarial security reviews by teams of AI agents, with findings and fixes published on the assurance page. Protocol choices (MLS, keystore-backed identity, client-side encryption) target a Slack-style workspace threat model, not a nation-state adversary. For high-risk use, pick a tool with a documented audit history.