docs

Ease docs/Quickstart

Quickstart

Connect, sign up a user, and send your first end-to-end encrypted message. The flow is the same on every SDK; the code examples below are language-selectable, so learn it once and switch to your language.

Install

Install the SDK for your platform, then follow the flow below. Each platform's page has the one-line install and its toolchain notes:

Use the language picker on any code block to switch between TypeScript, Swift, Kotlin, C#, and Rust; your choice follows you down the page. The flow is the same in every language, give or take each platform's idioms.

Set up your tenant

Everything server-side is configured once, in the Ease dashboard. Three choices there shape what your code does:

  • Authentication method: whether your users sign up with email, phone, or passkeys. This decides which SDK calls apply; calling the wrong family returns auth_method_mismatch.
  • OTP delivery provider: signup codes are sent through your own account with an email provider (Resend) or SMS provider (Twilio), connected in the dashboard. Until one is connected, sendSignupOtp fails with tenant_email_not_configured; wire the provider before testing sign-up.
  • API keys: the dashboard issues the publishable pk_test_ key the SDK boots with. It identifies your tenant and is safe to embed in client code; end-user auth is gated separately on each user's credentials.

For development, create a second tenant and treat it as your test tenant: point development builds at its key, keep your production tenant's key for release builds. Users, contacts, and messages never cross tenants, so test data stays out of production by construction.

Connect

Every Ease app starts with a tenant bootstrap: construct the client with your tenant's API key and call connect().

Web SDK
import { Ease } from '@securegroupchat/web-sdk';

const ease = new Ease({ apiKey: 'YOUR_API_KEY' });
await ease.connect();

connect() resolves the tenant's auth, profile, and socket URLs from the bootstrap service. You call it once per Ease instance. Everything else is user-scoped and lives under one of the four named surfaces: ease.me, ease.auth, ease.contacts, ease.chat.

Sign up

New users get a one-time code by email. Two calls: send the OTP, then sign up with the OTP and a password.

Web SDK
await ease.auth.sendSignupOtp('alice@example.com');

// User checks email, types the OTP into your form…
const session = await ease.auth.signUp(
  'alice@example.com',
  'a-strong-password',
  '123456', // the OTP
  { name: 'Alice' },
);

The returned Session is bound to the new user. Persist it and the SDK uses it automatically for every subsequent call; on the next launch, ease.auth.restoreSession(saved) skips the sign-in screen.

One web-specific caveat worth designing for up front: on web, restoreSession restores the session but not the encryption store (it is sealed with a password-derived key the SDK never persists), so profile and contacts work after a refresh while sending messages requires a fresh login with the password. The authentication concept page covers the full lifecycle.

Handle errors as you go

Every failure the SDK raises is an EaseError with a stable string code. Branch on the code, not the message. The two you will meet first are otp_invalid (wrong or expired code: let the user retype it) and not_connected (call connect() before anything else). The errors page catalogues every code by surface.

TypeScript
import { EaseError } from '@securegroupchat/web-sdk';

try {
  await ease.auth.signUp(email, password, otp, { name });
} catch (err) {
  if (err instanceof EaseError && err.code === 'otp_invalid') {
    // Wrong or expired code: surface the OTP field again.
  } else {
    throw err;
  }
}

Add a contact

DMs and groups gate on the contact graph: you can only DM someone who is in your contacts. Send a request, optionally with a welcome message, which is encrypted to the recipient.

Web SDK
await ease.contacts.sendRequest(
  { email: 'bob@example.com' },
  "Hey Bob, let's chat",
);

Bob's app receives the request through ease.contacts.onContactRequestReceived. When he accepts, you become mutual contacts and can DM from that point on.

Send your first DM

Open a DM room keyed by the peer's user ID. Subscribe before sending so inbound messages surface as they arrive.

Web SDK
const dm = ease.chat.dm('EUSR_alice');

dm.on('message', (event) => {
  if (event.kind !== 'message') return;
  const { envelope } = event.event;
  if (envelope.type === 'ease.text') {
    render(envelope.text);
  }
});

const ack = await dm.send({ type: 'ease.text', text: 'hello bob' });
console.log('sent', ack.messageId);

The message is libsignal-encrypted end to end. The server sees ciphertext, not plaintext; the handler on dm.on('message', …) receives the decrypted envelope when Bob replies.

Create a group

createGroup mints the group and returns its GRUP_ id; open the room by that id to send and receive.

Web SDK
const created = await ease.chat.createGroup(
  'Friday lunch',
  [bobUserId, carolUserId],
);
const room = ease.chat.group(created.groupId);

room.on('message', (event) => {
  if (event.kind !== 'message') return;
  const { envelope } = event.event;
  if (envelope.type === 'ease.text') render(envelope.text);
});

await room.send({ type: 'ease.text', text: 'pizza?' });

Groups need at least three members (creator plus two), each a contact of the creator. Group messages use libsignal Sender Keys; the SDK handles key distribution and your code never sees it.

See also