MailLandDocs

Team Management

Invite team members, assign roles, and manage permissions within your workspace

Collaborate with your team by inviting members to your workspace. Each member is assigned a role that determines their permissions.

Roles

RoleValueDescription
Owner0Full access, billing, can transfer ownership
Admin1Full access except billing and ownership transfer
Member2Send emails, manage domains, view analytics
Viewer3Read-only access to all resources

Role Permissions

Member is not read-only and not excluded from key management: it can create and edit almost everything. Only deleting API keys, domains and webhooks, plus team/ownership administration, is Admin-only.

PermissionOwnerAdminMemberViewer
Send emailsYesYesYesNo
Manage domainsYesYesYesNo
Manage subscribersYesYesYesNo
View analyticsYesYesYesYes
Create/rotate API keysYesYesYesNo
Delete API keys / domains / webhooksYesYesNoNo
Create/edit webhooksYesYesYesNo
Edit settings (including tracking)YesYesYesNo
Invite membersYesYesNoNo
Remove membersYesYesNoNo
Update rolesYesYesNoNo
Manage billingYesNoNoNo
Transfer ownershipYesNoNoNo
Delete workspaceYesNoNoNo

Team Overview

Get the current team members and pending invitations:

GET https://api.mail.land/v1/account/team
List team members
curl https://api.mail.land/v1/account/team \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Team overview response
{
  "members": [
    {
      "publicId": "mbr_a1b2c3d4e5",
      "email": "[email protected]",
      "fullName": "John Doe",
      "role": "owner",
      "status": "active",
      "joinedAt": "2026-01-15T08:00:00Z",
      "invitedByEmail": null,
      "isCurrentUser": true
    },
    {
      "publicId": "mbr_f6g7h8i9j0",
      "email": "[email protected]",
      "fullName": "Jane Smith",
      "role": "admin",
      "status": "active",
      "joinedAt": "2026-01-20T10:00:00Z",
      "invitedByEmail": "[email protected]",
      "isCurrentUser": false
    }
  ],
  "pendingInvitations": [
    {
      "publicId": "inv_k1l2m3n4o5",
      "email": "[email protected]",
      "role": "member",
      "status": "pending",
      "createdAt": "2026-02-17T08:00:00Z",
      "expiresAt": "2026-02-24T08:00:00Z",
      "invitedByEmail": "[email protected]",
      "invitedByName": "John Doe",
      "isExpired": false
    }
  ],
  "totalMembers": 2,
  "totalPendingInvitations": 1,
  "maxMembers": 10
}

There is no id, userId, firstName/lastName, roleName/statusName, invitedBy, memberCount or invitationCount in this response: use the field names above instead. maxMembers reflects the workspace's plan: 10 on Free and Pro, unlimited on Max. A seat is an active member (the owner included) or a pending, unexpired invitation; suspended members and expired, revoked or accepted invitations don't count. At the cap, inviting, accepting, reactivating or an SSO first login all return 400 PLAN_LIMIT_REACHED. Downgrading a plan never removes or suspends anyone: it only blocks new admissions while the workspace is over its new limit.

Member Statuses

StatusDescription
activeActive team member
suspendedSuspended by an admin

Invitation Statuses

Returned as lowercase strings:

StatusDescription
pendingAwaiting acceptance
acceptedInvitation accepted
expiredInvitation expired (7 days)
revokedInvitation revoked (not "cancelled")

Invite a Team Member

POST https://api.mail.land/v1/account/team/invite
Invite request
{
  "email": "[email protected]",
  "role": 2
}
FieldTypeRequiredDescription
emailstringYesEmail address to invite
roleintYesRole to assign (1–3, cannot invite as Owner)
Invite a team member
curl -X POST https://api.mail.land/v1/account/team/invite \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "role": 2 }'

The invited user receives an email with a link to accept the invitation. Invitations expire after 7 days.

Accept an Invitation

There is a single public route for accepting an invitation: it is not versioned (/v1 is not part of the path), and it is keyed by the invitation token, not an id:

POST https://api.mail.land/invitations/{token}/accept

For an existing MailLand user, sign in as the invited email address first, then call this route with an empty or minimal body, no firstName/lastName/password needed. For a brand-new user, provide fullName and password in the same call to create the account and accept in one step.

Accept invitation request (new user)
{
  "fullName": "New Member",
  "password": "SecureP@ssw0rd!"
}
Accept an invitation
curl -X POST https://api.mail.land/invitations/invitation-token-from-email/accept \
  -H "Content-Type: application/json" \
  -d '{"fullName": "New Member", "password": "SecureP@ssw0rd!"}'

There is no POST /v1/account/team/invitations/{id}/accept, no separate /accept-public route, and no GET /v1/team/invitations/{id}?token=. Both "existing user" and "new user" acceptance go through this one public /invitations/{token}/accept endpoint (Controllers/Public/InvitationsController.cs).

Get Invitation Info

Retrieve invitation details before accepting (public, no auth required, keyed by token, not /v1):

GET https://api.mail.land/invitations/{token}
Get invitation info
curl "https://api.mail.land/invitations/invitation-token-from-email"
Invitation info response
{
  "tenantName": "My Company",
  "role": "member",
  "invitedByName": "John Doe",
  "expiresAt": "2026-02-24T08:00:00Z",
  "isExpired": false,
  "alreadyAccepted": false,
  "existingUserEmail": "[email protected]"
}

Manage Invitations

Cancel an Invitation

DELETE https://api.mail.land/v1/account/team/invitations/{id}
Cancel an invitation
curl -X DELETE https://api.mail.land/v1/account/team/invitations/{id} \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Returns 204 No Content on success.

Resend an Invitation

POST https://api.mail.land/v1/account/team/invitations/{id}/resend
Resend an invitation
curl -X POST https://api.mail.land/v1/account/team/invitations/{id}/resend \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Resending an invitation resets the 7-day expiry.

Update a Member's Role

PATCH https://api.mail.land/v1/account/team/members/{id}/role
Update role request
{
  "role": 1
}
Update member role
curl -X PATCH https://api.mail.land/v1/account/team/members/{id}/role \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "role": 1 }'

Only Owners and Admins can update roles. You cannot change your own role or promote someone to Owner (use ownership transfer instead).

Admins can only manage Member/Viewer roles: they cannot promote a member to Admin or change another Admin's role. Only the Owner can do that.

Suspend and Unsuspend

Temporarily suspend a member's access:

POST https://api.mail.land/v1/account/team/members/{id}/suspend

A body { reason?, credentialDecision?, transferToMemberId? } controls what happens to that member's API keys while suspended. credentialDecision is only required when the member owns at least one live API key: omitting it in that case returns 400 CREDENTIAL_DECISION_REQUIRED, with the exposed keys listed in details. Otherwise the whole body may be omitted.

FieldValues
reasonoffboarding, compromise, abuse, other
credentialDecisionrevoke, keep, transfer
transferToMemberIdRequired when credentialDecision is transfer
Suspend a member
curl -X POST https://api.mail.land/v1/account/team/members/{id}/suspend \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Restore a suspended member:

POST https://api.mail.land/v1/account/team/members/{id}/reactivate
Unsuspend a member
curl -X POST https://api.mail.land/v1/account/team/members/{id}/reactivate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Suspended members cannot sign in to the workspace; their API keys follow the credential decision made at suspension time: with keep, the member's keys keep working.

Remove a Member

DELETE https://api.mail.land/v1/account/team/members/{id}

Removal accepts the same credential decision, as query parameters:

DELETE https://api.mail.land/v1/account/team/members/{id}?reason=offboarding&credentialDecision=revoke&transferToMemberId=mbr_f6g7h8i9j0

As with suspend, credentialDecision is required when the member owns a live API key and the call otherwise returns 400 CREDENTIAL_DECISION_REQUIRED.

Remove a member
curl -X DELETE https://api.mail.land/v1/account/team/members/{id} \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Removes the member from the workspace. They lose access to all workspace resources immediately.

Transfer Ownership

Transfer workspace ownership to another member:

POST https://api.mail.land/v1/account/team/transfer-ownership
Transfer ownership request
{
  "newOwnerMemberId": "mbr_f6g7h8i9j0"
}
Transfer ownership
curl -X POST https://api.mail.land/v1/account/team/transfer-ownership \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "newOwnerMemberId": "mbr_f6g7h8i9j0" }'

Ownership transfer is irreversible. The current owner is demoted to Admin, and the target member becomes the new Owner.

The target must be an active Admin (not a Member or Viewer).

Error Responses

StatusCodeDescription
400VALIDATION_FAILEDInvalid email or role
400INVALID_REQUESTMore than 50 pending invitations for this workspace
400CREDENTIAL_DECISION_REQUIREDSuspend/remove target owns live API keys and no credentialDecision was supplied
400CREDENTIAL_TRANSFER_TARGET_INVALIDcredentialDecision: transfer with a missing or invalid transferToMemberId
403FORBIDDENInsufficient permissions for this action
404RESOURCE_NOT_FOUNDMember or invitation not found
400PLAN_LIMIT_REACHEDWorkspace is at its plan's seat limit (invite, accept, reactivate, or SSO first login)
409CONFLICTUser is already a member, or already has a pending invitation

There is no MEMBER_ALREADY_EXISTS code: both "already a member" and "invitation already exists" return 409 CONFLICT.

POST /v1/account/team/leave (leave the workspace yourself) and GET /v1/account/team/me (your own membership record) also exist and are not documented elsewhere on this page.

Next Steps

On this page