---
title: "openemail.members"
description: "Every method in this namespace: its signature, its parameters, what it returns and an example."
url: "https://openemail.uk/docs/sdk/reference/members"
area: "SDK"
category: "Reference"
---

# openemail.members

Every method in this namespace: its signature, its parameters, what it returns and an example.

## Methods

People who share the workspace, their role and the addresses they may use, and the invitations still waiting.

### `members.list()`

List everybody with access to the workspace

```ts
list(options?: ListOptions): Promise<Page<MemberResource>>
```

Resolves one page of the workspace's members, the owner first and the rest by email. `listAll` collects every page and `iterate` walks them lazily. Each row carries two axes that a client must not merge: `role` and `permissions` say what the person may do, and `addresses` and `domains` say what they may do it to, each with its own `access`. A whole domain reaches every address on it, including ones made later. A send needs both axes, so `emails:send` with nothing granted sends from nothing.

The one exception is `addresses:all`. When `permissions` holds it, the person reaches every address on every domain of the workspace, including ones added later, and sends as any of them when `permissions` also holds `emails:send`. `addresses` and `domains` still list only the grants made to them directly, which they may have kept from before or been given since, or none at all. Read reach from `permissions` first, and from those two arrays only when it lacks `addresses:all`.

The list is a union of people with a membership row and people who only hold address or domain grants and no membership row. The second group comes back with `implied: true`, `role.id` null and `createdAt` null, and their role is inferred from their grants: Member if any grant has `access: 'member'`, Viewer otherwise. Until someone calls `update` for them, widening their addresses silently widens what they may do.

The workspace owner is the FIRST row, marked `isOwner: true`, so an unshared workspace returns one member rather than an empty page. They hold every permission by definition, and `add`, `update` and `remove` refuse them with `member_is_owner`. Exclude `isOwner` when counting seats.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): The `nextCursor` of the previous page. Leave it out for the first page.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`Page<MemberResource>` with `items`, `hasMore` and `nextCursor`. Each item has `userId`, `email`, `name`, `image`, `isOwner`, `role`, `implied`, `permissions`, `addresses`, `domains` and `createdAt`.

**Example**

```ts
const members = await openemail.members.listAll()

const unreviewed = members.filter((member) => member.implied)

console.log(unreviewed.map((member) => [member.email, member.role.name, member.addresses.length]))
```

**Notes**

- Every other method here takes `userId`, the account id, not the email address.
- `access: 'viewer'` on an address blocks sending from it even when the role holds `emails:send`, unless the role also holds `addresses:all`. Otherwise the two axes are ANDed, never added.
- The cursor is opaque and holds where the last row sat in this order, so a row deleted or edited between pages never breaks the walk: the next page starts at the first row that sorts after it. A cursor this list did not hand out is a 400 `invalid_cursor`.

Also available in: API [`GET /members`](https://openemail.uk/docs/api/reference/members#get-members); CLI [`openemail members list`](https://openemail.uk/docs/cli/reference/members#members-list).

### `members.listAll()`

Collect every member into one array

```ts
listAll(options?: ListOptions): Promise<Array<MemberResource>>
```

Walks every page of `list` and resolves with all members, the owner first and the rest by email. One request per page.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size for each request, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk after this cursor instead of the first page.
- `options.signal` (`AbortSignal`): Cancels the request in flight and the walk with it.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`Array<MemberResource>` holding every member.

**Example**

```ts
const all = await openemail.members.listAll({ limit: 100 })

console.log(all.length)
```

**Notes**

- If any page fails the promise rejects and the members already fetched are discarded.

Also available in: API [`GET /members`](https://openemail.uk/docs/api/reference/members#get-members).

### `members.iterate()`

Stream the members one at a time

```ts
iterate(options?: ListOptions): AsyncGenerator<MemberResource, void, undefined>
```

Returns an async generator that yields members individually, the owner first and the rest by email, and requests the next page only once the current one is drained. Nothing is fetched until you consume it, and breaking out of the loop stops the requests.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size for each request, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk after this cursor instead of the first page.
- `options.signal` (`AbortSignal`): Cancels the request in flight and the walk with it.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`AsyncGenerator<MemberResource, void, undefined>` yielding one member per step.

**Example**

```ts
for await (const item of openemail.members.iterate()) {
    console.log(item)
}
```

**Notes**

- The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /members`](https://openemail.uk/docs/api/reference/members#get-members).

### `members.get()`

Read one member by account id

```ts
get(userId: string, options?: RequestScope): Promise<MemberResource>
```

Resolves a single member with their role, resolved permissions and address grants. The lookup runs over the same union as `list`, so a legacy grant holder with no membership row is found here too, with `implied: true`.

The path takes the account id from `list`, not an email. An email can change on the account, and a stale one would point at the wrong person. The owner is not a member, so their account id answers 404 like any unknown one.

Scopes: `members:read`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with `role` (`id`, `name`, `builtin`), `implied`, the resolved `permissions`, and `addresses` holding `addressId`, `address` and `access`.

**Example**

```ts
const member = await openemail.members.get('q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E')

console.log(member.role.name, member.implied)
console.log(member.addresses.map((grant) => [grant.address, grant.access]))
```

**Notes**

- A missing member is 404 `resource_not_found`.
- For an implied member, `permissions` are the Member or Viewer template permissions, not a stored role that `roles.get` could read.

Also available in: API [`GET /members/{userId}`](https://openemail.uk/docs/api/reference/members#get-members-userid); CLI [`openemail members get`](https://openemail.uk/docs/cli/reference/members#members-get).

### `members.add()`

Invite somebody to the workspace with a role

```ts
add(body: MemberAdd, options?: RequestScope): Promise<InvitationResource>
```

Sends an invitation to join the workspace. Whether or not the address already has an OpenEmail account, the answer is an invitation rather than a member: nobody is put into a workspace without accepting, and this method is held to the same rule as the app.

The invitation carries the role, the addresses and the whole domains you name, and grants exactly those the moment it is accepted. Nothing is granted before that. Calling it again for the same address within ten minutes is refused with 409 `invitation_too_soon`; after that it refreshes the one outstanding invitation rather than sending a second.

Somebody already in the workspace is refused with 422 `member_is_owner`. Change what an existing member may do with `update`, `grantAddress` and `revokeAddress`, which only work on people already in.

Role and grants are separate axes. `access` applies to every id in `addressIds` and `domainIds`, and it never widens the role: `access: 'member'` under a role without `emails:send` still cannot send. A role holding `addresses:all` reaches every address without any ids, and no key or access token can invite with one, because none of them holds that console-only permission.

Scopes: `members:write`.

**Parameters**

- `body.email` (`string`, required): Who to invite. Trimmed and lowercased. It does not need to have an account yet.
- `body.roleId` (`string`, required): Role id from `roles.list`. The owner role is refused with 409 `role_immutable`, and a role holding more than the key itself holds is refused with 403 `insufficient_authority`. That covers every role with a console-only permission in it, such as `addresses:all`.
- `body.addressIds` (`Array<string>`): Up to 64 address ids on this workspace the invitation carries, all at `access`.
- `body.domainIds` (`Array<string>`): Up to 64 domain ids on this workspace the invitation carries, all at `access`. A whole domain covers every address on it, including ones made later.
- `body.access` (`MemberAccess`): `member` reads and sends as the granted addresses, `viewer` only reads them. Defaults to `member`.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`InvitationResource` with a 202: the invitation id, what it carries, when it expires, and `delivered`, which is false when the email did not leave the mail server.

**Example**

```ts
const invitation = await openemail.members.add({
    email: 'sam@acme.com',
    roleId: 'role_8b1f4c2e9a7d3b60e5f1a2c4',
    domainIds: ['93542ff8-2baa-4f2f-841d-5ceaa074ab0d'],
    access: 'member'
})

console.log(invitation.id, invitation.delivered, invitation.expiresAt)
```

**Notes**

- An unknown `roleId` is 404 `role_not_found` with `param` set to `roleId`. An address or domain id that is not on this workspace is 422 `member_not_found` with `param` set to `addressId` or `domainId`.
- The workspace owner, and anybody already in the workspace, is refused with 422 `member_is_owner`.
- `delivered: false` means the invitation exists but the email did not go out. It can be sent again from the app once the ten minute cooldown has passed.
- Not retried automatically. Re-posting the same body inside ten minutes is 409 rather than a duplicate.

Also available in: API [`POST /members`](https://openemail.uk/docs/api/reference/members#post-members); CLI [`openemail members add`](https://openemail.uk/docs/cli/reference/members#members-add).

### `members.update()`

Change the role a member holds

```ts
update(userId: string, patch: MemberPatch, options?: RequestScope): Promise<MemberResource>
```

Moves a member to a different role and changes nothing else, for somebody ALREADY in the workspace. Their address and domain grants are untouched, and grants cannot be patched here at all: `grantAddress` and `revokeAddress` change one grant at a time.

This is also how a legacy grant holder stops being implied. They have address or domain grants and no membership row, this writes one, and from then on `implied` is false and their permissions come from a role someone chose rather than from what their access implied.

Nobody joins through `update`. An account that is not in the workspace is 422 `member_not_found`: invite them with `add` and they are in once they accept. The owner role cannot be handed out, which is 409 `role_immutable`, and the workspace owner cannot be given a role, which is 422 `member_is_owner`.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `patch.roleId` (`string`, required): Id of the role to move them to.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with the new `role` and `permissions`, and `implied` set to false.

**Example**

```ts
const member = await openemail.members.update('q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E', {
    roleId: 'role_2c7e9a1f4b8d3e60c5a7f1b9'
})

console.log(member.role.name, member.implied)
```

**Notes**

- The account has to be in the workspace already, by membership row or by a grant. Anybody else is 422 `member_not_found`; use `add` to invite them.
- An unknown `roleId` is 404 `role_not_found`, and a user id with no OpenEmail account behind it is 404 `user_not_found`.
- A role holding more than the key itself holds is 403 `insufficient_authority`. No key or access token holds a console-only permission, so a role with `addresses:all`, billing or `workspace:manage` in it is handed out only in the app. That includes the seeded Admin, which holds billing.
- Retried automatically on network failure, since it names the role it wants and a replay lands on the same row.

Also available in: API [`PATCH /members/{userId}`](https://openemail.uk/docs/api/reference/members#patch-members-userid); CLI [`openemail members update`](https://openemail.uk/docs/cli/reference/members#members-update).

### `members.remove()`

Remove a member and every address grant they hold

```ts
remove(userId: string, options?: RequestScope): Promise<RemovedMemberResource>
```

Takes somebody out of the workspace entirely: the membership row and every address grant they hold on it. Removing only the row would drop them from the list while they kept reading the mail, so both go together.

It does not 404 for somebody who is not a member. The people this most needs to reach are legacy grant holders with no membership row, so there is no existence check, and `addressesRevoked` reports what actually happened. Zero is the honest answer for a no-op.

Their account, their sent mail and anything they wrote are untouched. Only their access to this workspace ends.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`RemovedMemberResource` with `object` set to `member`, the `userId`, `deleted: true` and `addressesRevoked`.

**Example**

```ts
const removed = await openemail.members.remove('q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E')

console.log(removed.deleted, removed.addressesRevoked)
```

**Notes**

- Idempotent on the server, but the SDK does not retry it automatically, so repeat it yourself after a network failure.
- The workspace owner is refused with 422 `member_is_owner`, because their access never came from a membership.

Also available in: API [`DELETE /members/{userId}`](https://openemail.uk/docs/api/reference/members#delete-members-userid); CLI [`openemail members remove`](https://openemail.uk/docs/cli/reference/members#members-remove).

### `members.grantAddress()`

Grant a member one address or change their access to it

```ts
grantAddress(userId: string, body: MemberAddressGrant, options?: RequestScope): Promise<MemberResource>
```

Gives one person one address on this workspace, or changes the access they already have to it. It is an upsert: there is one grant per person and address, so posting again with a different `access` turns a viewer into a member rather than adding a second grant.

This is the address axis and it cannot widen the role. `access: 'member'` lets somebody send as the address only if their role also holds `emails:send`, and otherwise it lets them read.

Granting an address to an account with no membership row makes them appear in `list` as an implied member whose role is inferred from their grants, so call `update` as well if the role should be a decision. The whole member comes back, which lets a client redraw the row without another read.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `body.addressId` (`string`, required): Id of an address on this workspace.
- `body.access` (`MemberAccess`): `member` reads and sends as the address, `viewer` only reads it. Defaults to `member`.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with the grant applied, including the full `addresses` list.

**Example**

```ts
const member = await openemail.members.grantAddress('q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E', {
    addressId: '4f0c1b6e-2d7a-4a9e-8c35-91b2e7d4f6a0',
    access: 'viewer'
})

console.log(member.addresses)
```

**Notes**

- An address id that is not on this workspace is 422 with code `member_not_found` and `param` set to `addressId`.
- The workspace owner is refused with 422 `member_is_owner`, since they already reach every address. A user id with no OpenEmail account behind it is 404 `user_not_found`.
- Gated on `members:write` rather than on owning the address, so an admin who owns nothing can still manage grants.
- Retried automatically on network failure, since replaying the same grant lands on the same row.

Also available in: API [`POST /members/{userId}/addresses`](https://openemail.uk/docs/api/reference/members#post-members-userid-addresses); CLI [`openemail members grant-address`](https://openemail.uk/docs/cli/reference/members#members-grant-address).

### `members.revokeAddress()`

Take one address back from a member

```ts
revokeAddress(userId: string, addressId: string, options?: RequestScope): Promise<MemberResource>
```

Removes one address grant and leaves the person in the workspace with their role and their other addresses. This is the narrow revocation to use when somebody changes team.

An address id that is not on this workspace is refused with 422 `member_not_found` rather than ignored, so a typo cannot report a revocation that never happened. An address that is on the workspace but was never granted to this person is a silent no-op, and the member comes back unchanged.

The member is returned rather than a tombstone, because the useful answer is what they can still reach.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `addressId` (`string`, required): Id of an address on this workspace.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with the remaining `addresses`.

**Example**

```ts
const member = await openemail.members.revokeAddress(
    'q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E',
    '4f0c1b6e-2d7a-4a9e-8c35-91b2e7d4f6a0'
)

console.log(member.addresses.map((grant) => grant.address))
```

**Notes**

- Revoking the last grant of an implied member removes them from the workspace entirely. The call still succeeds and returns the member as they stood, without that address.
- Somebody who is not a member of this workspace is a 404.
- Not retried automatically by the SDK.

Also available in: API [`DELETE /members/{userId}/addresses/{addressId}`](https://openemail.uk/docs/api/reference/members#delete-members-userid-addresses-addressid); CLI [`openemail members revoke-address`](https://openemail.uk/docs/cli/reference/members#members-revoke-address).

### `members.grantDomain()`

Grant a member a whole domain or change their access to it

```ts
grantDomain(userId: string, body: MemberDomainGrant, options?: RequestScope): Promise<MemberResource>
```

Gives one person every address on one domain of this workspace, including addresses added after the grant, or changes the access they already have to it. It is an upsert: there is one grant per person and domain, so calling it again with a different `access` turns a viewer into a member rather than adding a second grant.

This is the address axis and it cannot widen the role. `access: 'member'` lets somebody send from the domain only if their role also holds `emails:send`, and otherwise it lets them read.

The person has to be in the workspace already, so invite them with `add` first. The whole member comes back, with the grant in `domains`.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `body.domainId` (`string`, required): Id of a domain on this workspace, as `domains.list` returns it.
- `body.access` (`MemberAccess`): `member` reads and sends from the domain, `viewer` only reads it. Defaults to `member`.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with the grant applied, including the full `domains` list.

**Example**

```ts
const member = await openemail.members.grantDomain('q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E', {
    domainId: '7d2a9c41-5b3e-4f8a-9c06-1e2b3c4d5e6f',
    access: 'viewer'
})

console.log(member.domains)
```

**Notes**

- A domain id that is not on this workspace is 422 with code `member_not_found` and `param` set to `domainId`. Somebody who is not in the workspace yet is refused the same way with `param` set to `userId`.
- The workspace owner is refused with 422 `member_is_owner`, since they already reach every domain. A user id with no OpenEmail account behind it is 404 `user_not_found`.
- A workspace whose plan has no team access is refused with 403 `plan_required`. A key or an access token limited to particular addresses or domains is refused with 422 `capability_unsupported`, and an access token acting for a member can only give a domain that member reaches, or it is refused with 403 `insufficient_authority`.
- An OAuth access token needs a verification code for this call, and is refused with 403 `step_up_required` until the app has verified one in the last 60 minutes. `isStepUpRequired` on the error says so. An API key is never asked for a code.
- Retried automatically on network failure, since replaying the same grant lands on the same row.

Also available in: API [`POST /members/{userId}/domains`](https://openemail.uk/docs/api/reference/members#post-members-userid-domains); CLI [`openemail members grant-domain`](https://openemail.uk/docs/cli/reference/members#members-grant-domain).

### `members.revokeDomain()`

Take a whole domain back from a member

```ts
revokeDomain(userId: string, domainId: string, options?: RequestScope): Promise<MemberResource>
```

Removes one domain grant and leaves the person in the workspace with their role and their other grants. Addresses on that domain that were granted to them one by one stay granted.

A domain id that is not on this workspace is refused with 422 `member_not_found` rather than ignored, so a typo cannot report a revocation that never happened. A domain that is on the workspace but was never granted to this person is a silent no-op, and the member comes back unchanged.

The member is returned rather than a tombstone, because the useful answer is what they can still reach.

Scopes: `members:write`.

**Parameters**

- `userId` (`string`, required): The account id from `list`, not the email address.
- `domainId` (`string`, required): Id of a domain on this workspace.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`MemberResource` with the remaining `domains`.

**Example**

```ts
const member = await openemail.members.revokeDomain(
    'q7Vd3kX9mT2pLw8RzN4bYc6HfJ1sGa5E',
    '7d2a9c41-5b3e-4f8a-9c06-1e2b3c4d5e6f'
)

console.log(member.domains.map((grant) => grant.domain))
```

**Notes**

- Somebody who is not a member of this workspace is a 404.
- A key or an access token limited to particular addresses or domains is refused with 422 `capability_unsupported`.
- An OAuth access token needs a verification code for this call, and is refused with 403 `step_up_required` until the app has verified one in the last 60 minutes. `isStepUpRequired` on the error says so. An API key is never asked for a code.
- Not retried automatically by the SDK.

Also available in: API [`DELETE /members/{userId}/domains/{domainId}`](https://openemail.uk/docs/api/reference/members#delete-members-userid-domains-domainid); CLI [`openemail members revoke-domain`](https://openemail.uk/docs/cli/reference/members#members-revoke-domain).

### `members.listInvitations()`

List the invitations nobody has accepted yet

```ts
listInvitations(options?: ListOptions): Promise<Page<InvitationResource>>
```

Resolves one page of the invitations to this workspace that are still waiting, by email: the role and the addresses and whole domains each one grants once accepted, when it expires, and whether the last email reached them. `listAllInvitations` collects every page and `iterateInvitations` walks them lazily.

A waiting invitation grants nothing. It becomes access only at the moment somebody accepts it, which is why it is listed apart from `list`. An expired one stays on the list with `expired: true` until it is sent again or withdrawn.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): The `nextCursor` of the previous page. Leave it out for the first page.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`Page<InvitationResource>` with `items`, `hasMore` and `nextCursor`. Each item has `id`, `email`, `role`, `addresses`, `domains`, `expiresAt`, `expired`, `lastSentAt`, `delivered`, `deliveryError` and `createdAt`.

**Example**

```ts
const page = await openemail.members.listInvitations()

const stale = page.items.filter((invitation) => invitation.expired)
```

**Notes**

- `delivered` is null when no send outcome was recorded, false when the email did not leave the mail server, with the reason in `deliveryError`.
- A key limited to particular addresses or domains lists only the invitations that grant nothing outside them.

Also available in: API [`GET /members/invitations`](https://openemail.uk/docs/api/reference/members#get-members-invitations); CLI [`openemail members list-invitations`](https://openemail.uk/docs/cli/reference/members#members-list-invitations).

### `members.listAllInvitations()`

Collect every waiting invitation into one array

```ts
listAllInvitations(options?: ListOptions): Promise<Array<InvitationResource>>
```

Walks every page of `listInvitations` and resolves with every waiting invitation, by email. One request per page, with the same filters on each.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size for each request, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk after this cursor instead of the first page.
- `options.signal` (`AbortSignal`): Cancels the request in flight and the walk with it.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`Array<InvitationResource>` holding every waiting invitation.

**Example**

```ts
const all = await openemail.members.listAllInvitations()

console.log(all.map((invitation) => [invitation.email, invitation.role.name]))
```

**Notes**

- If any page fails the promise rejects and the rows already fetched are discarded.

Also available in: API [`GET /members/invitations`](https://openemail.uk/docs/api/reference/members#get-members-invitations).

### `members.iterateInvitations()`

Stream the waiting invitations one at a time

```ts
iterateInvitations(options?: ListOptions): AsyncGenerator<InvitationResource, void, undefined>
```

Returns an async generator that yields one invitation at a time, by email, and requests the next page only once the current one is drained. Nothing is fetched until you consume it, and breaking out of the loop stops the requests.

Scopes: `members:read`.

**Parameters**

- `options.limit` (`number`): Page size for each request, from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk after this cursor instead of the first page.
- `options.signal` (`AbortSignal`): Cancels the request in flight and the walk with it.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`AsyncGenerator<InvitationResource, void, undefined>` yielding one invitation per step.

**Example**

```ts
for await (const invitation of openemail.members.iterateInvitations()) {
    if (invitation.delivered === false) console.log(invitation.email, invitation.deliveryError)
}
```

**Notes**

- The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /members/invitations`](https://openemail.uk/docs/api/reference/members#get-members-invitations).

### `members.revokeInvitation()`

Withdraw an invitation

```ts
revokeInvitation(invitationId: string, options?: RequestScope): Promise<RevokedInvitationResource>
```

Withdraws an invitation nobody has accepted. Its link stops working at once and nothing it would have granted is granted. It is Withdraw on the members screen. Inviting the same address later with `add` sends a new one.

Scopes: `members:write`.

**Parameters**

- `invitationId` (`string`, required): The invitation id from `listInvitations`.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`RevokedInvitationResource`, `{ object: 'invitation', id, email, revoked: true }`.

**Example**

```ts
const page = await openemail.members.listInvitations()

for (const invitation of page.items.filter((item) => item.expired)) {
    await openemail.members.revokeInvitation(invitation.id)
}
```

**Notes**

- An invitation that was accepted first answers 409 `invitation_accepted`: remove the member instead. One that is unknown or already withdrawn answers 404.
- A key limited to particular addresses or domains can withdraw only an invitation that grants nothing outside them.

Also available in: API [`DELETE /members/invitations/{invitationId}`](https://openemail.uk/docs/api/reference/members#delete-members-invitations-invitationid); CLI [`openemail members revoke-invitation`](https://openemail.uk/docs/cli/reference/members#members-revoke-invitation).

### `members.resendInvitation()`

Send an invitation again

```ts
resendInvitation(invitationId: string, options?: RequestScope): Promise<InvitationResource>
```

Sends a waiting invitation again: a new link, fourteen more days, and the old link retired, so only the newest email works. It is Send again on the members screen, and it renews an expired invitation too.

Scopes: `members:write`.

**Parameters**

- `invitationId` (`string`, required): The invitation id from `listInvitations`.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client API key for this call only.

**Returns**

`InvitationResource` with the new `expiresAt` and `lastSentAt`, and `delivered` for this send.

**Example**

```ts
const invitation = await openemail.members.resendInvitation('winv_6bb640f5b99e47deb758f1f5')

console.log(invitation.expiresAt, invitation.delivered)
```

**Notes**

- The same address cannot be sent to twice within ten minutes: that is 409 `invitation_too_soon`. The daily limit on invitations is 409 `invitation_limit_reached`.
- The invitation's role cannot hold more than the key does, the rule `add` applies too: a role with a permission the key or access token lacks, any console-only one such as `addresses:all` included, is 403 `insufficient_authority`. An access token acting for a member is also refused with it when the invitation carries addresses or domains that member does not reach.
- The SDK does not retry it, because each call sends an email.

Also available in: API [`POST /members/invitations/{invitationId}/resend`](https://openemail.uk/docs/api/reference/members#post-members-invitations-invitationid-resend); CLI [`openemail members resend-invitation`](https://openemail.uk/docs/cli/reference/members#members-resend-invitation).
