# Connect multiple accounts per user

Source: https://developer.nylas.com/docs/cookbook/use-cases/build/multi-account-per-user/

One person often has more than one mailbox: a work Gmail account, a personal iCloud account, and maybe a shared Outlook account for their team. Your product needs all of them connected under a single login, so the user sees every account in one place and you can read, send, and schedule across them. This recipe shows how to model several connected accounts for the same user and act across them.

The thing to understand up front: each connected account is a separate [grant](/docs/v3/auth/), and there is no Nylas object that groups grants into a "user." That grouping lives in your own database. Below covers how to model it, where the mapping goes, and how to fan out requests across a user's accounts. For the single-account flow itself, see [connect user accounts with OAuth](/docs/cookbook/use-cases/build/connect-user-accounts-oauth/).

## How do I connect multiple accounts for one user?

Run the standard OAuth flow once per mailbox the user adds. Each completed flow returns 1 grant ID, and you append it to that user's list of grants in your database. A user with 3 mailboxes ends up with 3 grant IDs mapped to a single user record, and those mailboxes can span all 6 providers. There is no separate multi-account endpoint to call.

Every account uses the same hosted authorization flow, so connecting a second or third mailbox is the exact same code path as the first. The only thing that changes is which provider the user picks. The `state` parameter is what ties the callback back to the right person, but it must be an unguessable, single-use token, never the raw user ID. A predictable `state` lets an attacker forge a callback that links their own mailbox to another user's account. Mint a random `state`, store it server-side mapped to the user with a short expiry, and verify it when the callback fires before linking the grant.

```js [addAccount-Node.js SDK]


const state = crypto.randomBytes(16).toString("hex"); // unguessable, single-use
await db.oauthStates.create({ state, userId: user.id, expiresAt: Date.now() + 600_000 });

const authUrl = nylas.auth.urlForOAuth2({
  clientId: process.env.NYLAS_CLIENT_ID,
  provider: "icloud", // or "google", "microsoft"
  redirectUri: "https://myapp.com/callback",
  state, // verify and consume this on the callback before linking the grant
});

res.redirect(authUrl);
```

The full token exchange is covered in [hosted OAuth with an API key](/docs/v3/auth/hosted-oauth-apikey/).

## How do I store the grant-to-user mapping?

Store a one-to-many relationship: 1 user record points to many grant rows. Each grant row holds the `grant_id`, the provider, the account email, and a label like "Work" or "Personal." Nylas keeps no concept of a user, so this table is the only place the accounts are tied together. Losing it means losing the grouping.

A simple grants table keyed by your user ID does the job. When the OAuth callback fires, you read the `state` to recover the user ID, exchange the code for a grant, then insert one row linking that user to the new grant. You never store provider tokens, only the grant ID and your own metadata. Keep the email address on the row so the UI can label each account without an extra API call, and add a `unique` constraint on `grant_id` so the same mailbox isn't linked twice.

```sql
CREATE TABLE user_grants (
  id          BIGSERIAL PRIMARY KEY,
  user_id     BIGINT NOT NULL REFERENCES users(id),
  grant_id    TEXT NOT NULL UNIQUE,
  provider    TEXT NOT NULL,
  email       TEXT NOT NULL,
  label       TEXT,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

This split keeps the riskiest data out of your database, as described in [connect user accounts with OAuth](/docs/cookbook/use-cases/build/connect-user-accounts-oauth/).

## How do I act across a user's accounts?

Every Nylas request targets one grant, so acting across a user means looking up their grant IDs and issuing one request per grant. A user with 3 connected mailboxes means 3 calls to list messages, 3 calls to list calendars, and so on. There is no single call that spans a user's accounts, so you fan out in your own code and combine the results.

The pattern is the same for any endpoint: read the user's grant IDs from your table, map over them, and run the per-grant request in parallel. The example below lists the most recent 10 messages from each of a user's mailboxes. The call shape is identical across providers, so a Gmail grant and an iCloud grant use the same code with no provider branching. Tag each result with its grant ID so the user can see which account it came from.

```js [actAcross-Node.js SDK]
// Rows expose the grant_id column as grantId here via your data layer's snake_case-to-camelCase mapping.
const grants = await db.userGrants.findByUserId(user.id);

const results = await Promise.all(
  grants.map(async ({ grantId, label }) => {
    const { data } = await nylas.messages.list({
      identifier: grantId,
      queryParams: { limit: 10 },
    });
    return data.map((message) => ({ ...message, grantId, label }));
  }),
);
```

To render those merged results in a single combined view, see [build a unified inbox](/docs/cookbook/email/unified-inbox/).

## Things to know about multiple accounts per user

A few details matter once a user moves past a single connected mailbox. Most of them come back to the same fact: the user-to-grants grouping is yours to maintain, and Nylas only ever sees individual grants.

**Rate limits apply per grant, not per user.** Google throttles at the per-user and per-project level, and Microsoft throttles per mailbox, so fanning out across a user's 3 accounts counts as 3 separate quotas. Use [webhooks](/docs/cookbook/use-cases/build/new-email-webhook/) instead of polling each grant on a timer, which spares you a stream of redundant requests and stays well under those limits across all 6 providers.

**A grant can expire on its own.** If a user changes their work password or an admin revokes access, that single grant goes invalid while the user's other accounts keep working. Track grant status per row and prompt the user to reconnect just the affected mailbox. See [handle grant expiry](/docs/cookbook/use-cases/build/handle-grant-expiry/).

**Disconnecting one account is a single delete.** When a user removes their personal mailbox, revoke that grant and delete only its row, leaving the other 2 connected. The grouping stays intact because each account is independent.

## What's next

- [Connect user accounts with OAuth](/docs/cookbook/use-cases/build/connect-user-accounts-oauth/) for the single-account flow, scopes, and revocation
- [Bulk-authenticate user accounts](/docs/cookbook/use-cases/build/bulk-authenticate-accounts/) to connect many users across an organization at once
- [Build a unified inbox](/docs/cookbook/email/unified-inbox/) to merge a user's accounts into one view
- [Handle grant expiry and re-authentication](/docs/cookbook/use-cases/build/handle-grant-expiry/) to keep each connected account healthy
- [Authentication overview](/docs/v3/auth/) for the full grant model