Directory
Defines an external directory for account authentication and lookups.
This object can be configured from the WebUI under Settings › Authentication › Directories
Fields
Section titled “Fields”Directory is a multi-variant object: each instance has an @type discriminator selecting one of the variants below, and each variant carries its own set of fields.
@type: "Ldap"
Section titled “@type: "Ldap"”LDAP Directory
description
Section titled “description”Type:
String· requiredShort description of this directory
Type:
Uri· default:"ldap://localhost:389"URL of the LDAP server
urlFailover
Section titled “urlFailover”Type:
Set<Uri>· enterpriseURLs of backup LDAP servers, tried in order when the primary server is unreachable
timeout
Section titled “timeout”Type:
Duration· default:30000Timeout for establishing a connection to the LDAP server and for each LDAP operation
allowInvalidCerts
Section titled “allowInvalidCerts”Type:
Boolean· default:falseAllow invalid TLS certificates when connecting to the server
useTls
Section titled “useTls”Type:
Boolean· default:falseUse TLS to connect to the remote server
baseDn
Section titled “baseDn”Type:
String· requiredThe base distinguished name (DN) from where searches should begin
bindDn
Section titled “bindDn”Type:
String?The distinguished name of the account that the server will bind as to connect to the LDAP directory
bindSecret
Section titled “bindSecret”Type:
SecretKeyOptional· default:{"@type":"None"}The password or secret for the bind DN account
bindAuthentication
Section titled “bindAuthentication”Type:
Boolean· default:trueWhether to use bind authentication. When enabled, the server will use the filterLogin to search for the user account and then attempt to bind as that account using the provided password. When disabled, the server will use the bind DN and secret to connect to the LDAP server and obtain the secret from the account entry using the attrSecret attribute.
filterLogin
Section titled “filterLogin”Type:
String· default:"(&(objectClass=inetOrgPerson)(mail=?))"Searches for user accounts by e-mail address during authentication
filterRecipient
Section titled “filterRecipient”Type:
String· default:"(|(&(objectClass=inetOrgPerson)(|(mail=?)(mailAlias=?)))(&(objectClass=groupOfNames)(|(mail=?)(mailAlias=?))))"Searches for users or groups matching a recipient e-mail address or alias
filterMemberOf
Section titled “filterMemberOf”Type:
String?· default:"(&(objectClass=groupOfNames)(member=?))"Searches for groups that an account is member of. Use when the group membership is not provided in the account entry. The ? in the filter will be replaced with the account DN.
attrClass
Section titled “attrClass”Type:
Set<String>· default:{"objectClass":true}LDAP attribute for the user’s account type, if missing defaults to individual.
attrDescription
Section titled “attrDescription”Type:
Set<String>· default:{"description":true}LDAP attributes used to store the user’s description
attrEmail
Section titled “attrEmail”Type:
Set<String>· default:{"mail":true}LDAP attribute for the user’s primary email address
attrEmailAlias
Section titled “attrEmailAlias”Type:
Set<String>· default:{"mailAlias":true}LDAP attribute for the user’s email alias(es)
attrMemberOf
Section titled “attrMemberOf”Type:
Set<String>· default:{"memberOf":true}LDAP attributes for the groups that a user belongs to. Used when filterMemberOf is not configured or when the group membership is also provided in the account entry.
attrSecret
Section titled “attrSecret”Type:
Set<String>· default:{"userPassword":true}LDAP attribute for the user’s password hash. This setting is required when binding as a service user. When using bind authentication, configure the secret-changed attribute instead.
attrSecretChanged
Section titled “attrSecretChanged”Type:
Set<String>· default:{"pwdChangeTime":true}LDAP attribute that provides a password change hash or a timestamp indicating when the password was last changed. When using bind authentication, this attribute is used to determine when to invalidate OAuth tokens.
groupClass
Section titled “groupClass”Type:
String· default:"groupOfNames"LDAP object class used to identify group entries
poolMaxConnections
Section titled “poolMaxConnections”Type:
UnsignedInt· default:10· max: 8192Maximum number of connections that can be maintained simultaneously in the connection pool
poolTimeoutCreate
Section titled “poolTimeoutCreate”Type:
Duration· default:30000Maximum amount of time that the connection pool will wait for a new connection to be created
poolTimeoutRecycle
Section titled “poolTimeoutRecycle”Type:
Duration· default:30000Maximum amount of time that the connection pool manager will wait for a connection to be recycled
poolTimeoutWait
Section titled “poolTimeoutWait”Type:
Duration· default:30000Maximum amount of time that the connection pool will wait for a connection to become available
memberTenantId
Section titled “memberTenantId”Type:
Id<Tenant>?· enterpriseIdentifier for the tenant this directory belongs to
@type: "Sql"
Section titled “@type: "Sql"”SQL Database
description
Section titled “description”Type:
String· requiredShort description of this directory
Type:
SqlAuthStore· default:{"@type":"Default"}Storage backend where accounts and groups are stored
columnEmail
Section titled “columnEmail”Type:
String· default:"name"Column holding the account’s primary e-mail address. When the column is missing from the result or does not hold an address, the login name is used as the address at sign-in, and recipient lookups find no aliases or groups.
columnSecret
Section titled “columnSecret”Type:
String· default:"secret"Column name for the account password.
columnClass
Section titled “columnClass”Type:
String?· default:"type"Column name for account type
columnDescription
Section titled “columnDescription”Type:
String?· default:"description"Column name for account full name or description
queryLogin
Section titled “queryLogin”Type:
String· default:"SELECT name, secret, description, type FROM accounts WHERE name = $1 AND active = true"Query to obtain the account details by login e-mail address.
queryRecipient
Section titled “queryRecipient”Type:
String· default:"SELECT name, secret, description, type FROM accounts WHERE name = $1 AND active = true"Query to obtain the account details by recipient e-mail address or alias.
queryMemberOf
Section titled “queryMemberOf”Type:
String?· default:"SELECT member_of FROM group_members WHERE name = $1"Query to obtain the groups an account is member of.
queryEmailAliases
Section titled “queryEmailAliases”Type:
String?· default:"SELECT address FROM emails WHERE name = $1"Query to obtain the e-mail aliases of an account.
memberTenantId
Section titled “memberTenantId”Type:
Id<Tenant>?· enterpriseIdentifier for the tenant this directory belongs to
@type: "Oidc"
Section titled “@type: "Oidc"”OpenID Connect
description
Section titled “description”Type:
String· requiredShort description of this directory
issuerUrl
Section titled “issuerUrl”Type:
Uri· requiredThe issuer URL of the OpenID Connect provider (e.g. https://sso.example.com/realms/myrealm). It is compared exactly with the issuer reported by the provider, so enter it exactly as the provider reports it, including any trailing slash. Endpoints are discovered from /.well-known/oauth-authorization-server first and then from /.well-known/openid-configuration. For Microsoft Entra ID, use the tenant-specific issuer.
allowInvalidCerts
Section titled “allowInvalidCerts”Type:
Boolean· default:falseAllow invalid TLS certificates when connecting to the OpenID Connect provider
claimUsername
Section titled “claimUsername”Type:
String· default:"preferred_username"The claim name used to retrieve the user’s login name from the token or user info response. Common values are preferred_username, email, or sub depending on the provider’s configuration. If the claim value is not an email address and usernameDomain is set, the domain will be appended automatically (e.g. john becomes [email protected]). If the claim value already contains an @, it is used as-is. If the claim value is not an email address and no usernameDomain is configured, Stalwart will fall back to the email claim. If neither yields a valid email address, authentication will be rejected.
usernameDomain
Section titled “usernameDomain”Type:
String?The domain name to append to the username when the value of claimUsername does not contain an @ symbol (e.g. setting this to example.com will turn john into [email protected]). If not set, Stalwart will fall back to the email claim when the username claim does not contain a valid email address.
claimName
Section titled “claimName”Type:
String?The claim name used to retrieve the user’s display name from the token or user info response. Common values are name or display_name. If not set, the display name will not be populated. When it is set and the access token does not carry the claim, every login calls the provider’s user info endpoint to fetch it, so leave it unset unless the provider includes it in access tokens or the extra request is acceptable.
claimGroups
Section titled “claimGroups”Type:
String?The claim name used to retrieve the user’s group memberships from the token or user info response. Common values are groups or roles depending on the provider’s configuration. If not set, group information will not be populated. Note that some providers omit group claims from the token to keep its size small and only return them via the user info endpoint. When it is set and the access token does not carry the claim, every login calls the user info endpoint to fetch it.
memberTenantId
Section titled “memberTenantId”Type:
Id<Tenant>?· enterpriseIdentifier for the tenant this directory belongs to
acceptedAudiences
Section titled “acceptedAudiences”Type:
Set<String>Additional values accepted in the token’s aud claim besides this server’s resource identifiers. Use it for the audience the identity provider puts in access tokens, for example the client id of the web interface, an API identifier, or a project id.
opaqueTokenValidation
Section titled “opaqueTokenValidation”Type:
OidcOpaqueTokenValidation· default:"none"How access tokens that are not JWTs are validated. Disabled rejects them. User info calls the provider’s UserInfo endpoint, which cannot check the token’s audience or scopes, so any token the provider issued to any application is accepted, with the access set by the tokens without scopes option. Introspection calls the provider’s RFC 7662 introspection endpoint, checks audience and scopes, and requires client credentials. Only one method is used per token.
introspectionClientId
Section titled “introspectionClientId”Type:
String?Client id used to authenticate to the introspection endpoint
introspectionClientSecret
Section titled “introspectionClientSecret”Type:
SecretKeyOptional· default:{"@type":"None"}Client secret used to authenticate to the introspection endpoint
introspectionAuthMethod
Section titled “introspectionAuthMethod”Type:
OidcIntrospectionAuth· default:"clientSecretBasic"How the server authenticates to the introspection endpoint
unscopedTokens
Section titled “unscopedTokens”Type:
OidcUnscopedTokenPolicy· default:"reject"How access tokens that carry none of the interoperable mail, contacts, calendars or files scopes are handled. Tokens issued to this server’s web interface always have full access. Reject follows the OAuth profile for open public clients. Interoperable services grants access to mail, contacts, calendars and files, which is enough for mail clients when the provider cannot issue these scopes. Full access also grants management permissions to any such token and should only be used together with a strict audience check.
skipAudienceValidation
Section titled “skipAudienceValidation”Type:
Boolean· default:falseAccept access tokens whose aud claim is missing or matches none of this server’s resource identifiers or accepted audiences. Tokens issued to any application at the provider will then be accepted.
allowIdTokenClaims
Section titled “allowIdTokenClaims”Type:
Boolean· default:falseAccept JWT access tokens that carry ID token claims such as nonce or at_hash. Needed for providers whose access tokens are copies of the ID token. When enabled, an ID token can be presented as an access token.
lenientIssuerMatch
Section titled “lenientIssuerMatch”Type:
Boolean· default:falseIgnore a trailing slash difference between the configured issuer URL and the issuer reported by the provider and found in tokens
JMAP API
Section titled “JMAP API”The Directory object is available via the urn:stalwart:jmap capability.
x:Directory/get
Section titled “x:Directory/get”This is a standard Foo/get method as defined in RFC 8620, Section 5.1.
This method requires the sysDirectoryGet permission.
curl -X POST https://mail.example.com/jmap/ \ -H 'Authorization: Bearer $TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "methodCalls": [ [ "x:Directory/get", { "ids": [ "id1" ] }, "c1" ] ], "using": [ "urn:ietf:params:jmap:core", "urn:stalwart:jmap" ] }'x:Directory/set
Section titled “x:Directory/set”This is a standard Foo/set method as defined in RFC 8620, Section 5.3.
Supports the following operations in a single call: create, update, destroy.
Create
Section titled “Create”This operation requires the sysDirectoryCreate permission.
curl -X POST https://mail.example.com/jmap/ \ -H 'Authorization: Bearer $TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "methodCalls": [ [ "x:Directory/set", { "create": { "new1": { "@type": "Ldap", "baseDn": "Example", "bindSecret": { "@type": "None" }, "description": "Example", "urlFailover": {} } } }, "c1" ] ], "using": [ "urn:ietf:params:jmap:core", "urn:stalwart:jmap" ] }'Update
Section titled “Update”This operation requires the sysDirectoryUpdate permission.
curl -X POST https://mail.example.com/jmap/ \ -H 'Authorization: Bearer $TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "methodCalls": [ [ "x:Directory/set", { "update": { "id1": { "description": "updated value" } } }, "c1" ] ], "using": [ "urn:ietf:params:jmap:core", "urn:stalwart:jmap" ] }'Destroy
Section titled “Destroy”This operation requires the sysDirectoryDestroy permission.
curl -X POST https://mail.example.com/jmap/ \ -H 'Authorization: Bearer $TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "methodCalls": [ [ "x:Directory/set", { "destroy": [ "id1" ] }, "c1" ] ], "using": [ "urn:ietf:params:jmap:core", "urn:stalwart:jmap" ] }'x:Directory/query
Section titled “x:Directory/query”This is a standard Foo/query method as defined in RFC 8620, Section 5.5.
This method requires the sysDirectoryQuery permission.
curl -X POST https://mail.example.com/jmap/ \ -H 'Authorization: Bearer $TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "methodCalls": [ [ "x:Directory/query", { "filter": { "memberTenantId": "id1" } }, "c1" ] ], "using": [ "urn:ietf:params:jmap:core", "urn:stalwart:jmap" ] }'The x:Directory/query filter argument accepts the following conditions (combinable with AnyOf / AllOf / Not per RFC 8620):
| Condition | Kind |
|---|---|
memberTenantId | id of Tenant |
stalwart-cli wraps the same JMAP calls. See the CLI reference for installation, authentication, and general usage.
stalwart-cli get Directory id1Create
Section titled “Create”stalwart-cli create Directory/Ldap \ --field description=Example \ --field 'urlFailover={}' \ --field baseDn=Example \ --field 'bindSecret={"@type":"None"}'stalwart-cli query Directorystalwart-cli query Directory --where memberTenantId=id1Update
Section titled “Update”stalwart-cli update Directory id1 --field description='updated value'Delete
Section titled “Delete”stalwart-cli delete Directory --ids id1Nested types
Section titled “Nested types”SecretKeyOptional
Section titled “SecretKeyOptional”An optional secret value, or none.
None: No secret. No additional fields.Value: Secret value. Carries the fields ofSecretKeyValue.EnvironmentVariable: Secret read from environment variable. Carries the fields ofSecretKeyEnvironmentVariable.File: Secret read from file. Carries the fields ofSecretKeyFile.
SecretKeyValue
Section titled “SecretKeyValue”A secret value provided directly.
secret
Section titled “secret”Type:
String· required · secretPassword or secret value
SecretKeyEnvironmentVariable
Section titled “SecretKeyEnvironmentVariable”A secret value read from an environment variable.
variableName
Section titled “variableName”Type:
String· requiredEnvironment variable name to read the secret from
SecretKeyFile
Section titled “SecretKeyFile”A secret value read from a file.
filePath
Section titled “filePath”Type:
String· requiredFile path to read the secret from
SqlAuthStore
Section titled “SqlAuthStore”Defines the SQL database used to store account and group information for SQL directories.
Default: Use data store (SQL only). No additional fields.PostgreSql: PostgreSQL. Carries the fields ofPostgreSqlStore.MySql: mySQL. Carries the fields ofMySqlStore.Sqlite: SQLite. Carries the fields ofSqliteStore.
PostgreSqlStore
Section titled “PostgreSqlStore”PostgreSQL data store.
connectTimeout
Section titled “connectTimeout”Type:
Duration?· default:15000Maximum time to wait for a connection to the database server to be established
useTls
Section titled “useTls”Type:
Boolean· default:falseUse TLS to connect to the store
allowInvalidCerts
Section titled “allowInvalidCerts”Type:
Boolean· default:falseAllow invalid TLS certificates when connecting to the store
poolMaxConnections
Section titled “poolMaxConnections”Type:
UnsignedInt?· max: 8192 · min: 1Maximum number of connections to the store. When unset, 4 per CPU core, between 16 and 32.
poolRecyclingMethod
Section titled “poolRecyclingMethod”Type:
PostgreSqlRecyclingMethod· default:"fast"Method to use when recycling connections in the pool
readReplicas
Section titled “readReplicas”Type:
List<PostgreSqlSettings>· enterpriseList of read replicas for the store
Type:
String· requiredHostname of the database server
Type:
UnsignedInt· default:5432· max: 65535 · min: 1Port of the database server
database
Section titled “database”Type:
String· default:"stalwart"Name of the database
authUsername
Section titled “authUsername”Type:
String?· default:"stalwart"Username to connect to the store
authSecret
Section titled “authSecret”Type:
SecretKeyOptional· default:{"@type":"None"}Password to connect to the store
options
Section titled “options”Type:
String?Additional connection options
PostgreSqlSettings
Section titled “PostgreSqlSettings”PostgreSQL connection settings.
Type:
String· requiredHostname of the database server
Type:
UnsignedInt· default:5432· max: 65535 · min: 1Port of the database server
database
Section titled “database”Type:
String· default:"stalwart"Name of the database
authUsername
Section titled “authUsername”Type:
String?· default:"stalwart"Username to connect to the store
authSecret
Section titled “authSecret”Type:
SecretKeyOptional· default:{"@type":"None"}Password to connect to the store
options
Section titled “options”Type:
String?Additional connection options
MySqlStore
Section titled “MySqlStore”MySQL data store.
idleTimeout
Section titled “idleTimeout”Type:
Duration?· default:15000Time an idle connection may stay open before the server closes it (sets the MySQL wait_timeout session variable)
useTls
Section titled “useTls”Type:
Boolean· default:falseUse TLS to connect to the store
allowInvalidCerts
Section titled “allowInvalidCerts”Type:
Boolean· default:falseAllow invalid TLS certificates when connecting to the store
maxAllowedPacket
Section titled “maxAllowedPacket”Type:
UnsignedInt?· max: 1073741824 · min: 1024Maximum size of a packet in bytes
poolMaxConnections
Section titled “poolMaxConnections”Type:
UnsignedInt?· max: 8192 · min: 1Maximum number of connections to the store. When unset, 4 per CPU core, between 16 and 32.
poolMinConnections
Section titled “poolMinConnections”Type:
UnsignedInt?· default:5· max: 8192 · min: 1Minimum number of connections to the store
readReplicas
Section titled “readReplicas”Type:
List<MySqlSettings>· enterpriseList of read replicas for the store
Type:
String· requiredHostname of the database server
Type:
UnsignedInt· default:3306· max: 65535 · min: 1Port of the database server
database
Section titled “database”Type:
String· default:"stalwart"Name of the database
authUsername
Section titled “authUsername”Type:
String?· default:"stalwart"Username to connect to the store
authSecret
Section titled “authSecret”Type:
SecretKeyOptional· default:{"@type":"None"}Password to connect to the store
MySqlSettings
Section titled “MySqlSettings”MySQL connection settings.
Type:
String· requiredHostname of the database server
Type:
UnsignedInt· default:3306· max: 65535 · min: 1Port of the database server
database
Section titled “database”Type:
String· default:"stalwart"Name of the database
authUsername
Section titled “authUsername”Type:
String?· default:"stalwart"Username to connect to the store
authSecret
Section titled “authSecret”Type:
SecretKeyOptional· default:{"@type":"None"}Password to connect to the store
SqliteStore
Section titled “SqliteStore”SQLite embedded data store.
Type:
String· requiredPath to the SQLite data directory
poolWorkers
Section titled “poolWorkers”Type:
UnsignedInt?· max: 64 · min: 1Number of worker threads to use for the store, defaults to the number of cores
poolMaxConnections
Section titled “poolMaxConnections”Type:
UnsignedInt?· max: 8192 · min: 1Maximum number of connections to the store. When unset, the number of worker threads, between 4 and 16.
PostgreSqlRecyclingMethod
Section titled “PostgreSqlRecyclingMethod”| Value | Label |
|---|---|
fast | Fast recycling method |
verified | Verified recycling method |
clean | Clean recycling method |
OidcOpaqueTokenValidation
Section titled “OidcOpaqueTokenValidation”| Value | Label |
|---|---|
none | Disabled |
userInfo | UserInfo endpoint (no audience or scope checks) |
introspection | Token introspection (RFC 7662) |
OidcIntrospectionAuth
Section titled “OidcIntrospectionAuth”| Value | Label |
|---|---|
clientSecretBasic | HTTP Basic (client_secret_basic) |
clientSecretPost | Form parameters (client_secret_post) |
none | No authentication |
OidcUnscopedTokenPolicy
Section titled “OidcUnscopedTokenPolicy”| Value | Label |
|---|---|
reject | Reject |
interoperable | Interoperable services only |
full | Full access (unsafe) |