Connected email accounts
This capability is available for demo purposes only. Its navigation entry is feature-flagged and hidden in the sandbox and production environments.
Brokers can connect their email accounts (Gmail or Outlook) to Korint. The connected email accounts page gives a logged-in broker a single view of the accounts available to them, so they can tell at a glance which mailboxes are linked and whether they are personal or shared with colleagues.
The page lives in the intermediary dashboard under Emails → Gestion des comptes.
What a broker sees
The page requires an authenticated broker within a tenant, and it lists two groups:
- Mes comptes: the broker's personal accounts (
isShared: false). - Comptes partagés: the brokerage firm's shared accounts (
isShared: true), including those connected by the logged-in broker. Shared accounts belong to the firm; each row shows who last connected the account.
Both sections remain visible when empty. The sections use the sharing mode, not the identity of the person who connected the account. There is no lock or sharing toggle.
Within each group, accounts are ordered by connection date, oldest first. Every row shows the provider logo, the account name, its email address, and a status indicator for the connection health.
The page shows a loading state while fetching the list, an empty state in each section with no accounts, and an error state when the list cannot be loaded.
Connect and manage accounts
Each section has its own Ajouter button. The shared section's button is visible only when the broker has the shared-account management permission. Both buttons open the same provider dialog with three choices: Gmail, Outlook, and Boîte partagée Microsoft 365. The personal section starts a personal connection; the shared section starts a shared connection and explains that the mailbox, including its history, is shared with the firm.
A Microsoft 365 shared mailbox has no login of its own, so the third choice first asks for the mailbox address, then sends the broker to Microsoft to sign in with their own account, which must have full access to that mailbox. Korint binds that sign-in to the shared mailbox: the connected account carries the mailbox address, not the broker's, and its mail is synchronized directly. This choice is independent of the section: a Microsoft 365 shared mailbox can be connected as a personal account, and a regular Outlook mailbox as a shared one. Reconnecting such an account repeats the same delegated sign-in.
You can disconnect your personal accounts and reconnect them when their status
is error. Shared-account management requires the firm's
EMAIL_ACCOUNT_SHARED_ADMIN permission, even for the broker who originally
connected the account. A permitted administrator can disconnect or reconnect
a shared account connected by a colleague. A disconnected status indicates
an automatic retry and does not show the reconnect action.
A mailbox can be connected only once per brokerage firm for a given provider and email address. To change its sharing mode, disconnect it, then use Ajouter in the other section. This reuses the deleted account and applies the selected sharing mode to its history.
Feature flag and environment policy
Visibility is controlled by the feat_connected_email_accounts feature flag,
resolved through the in-house feature-flag registry. The flag is a row in the
FeatureFlag table, evaluated per environment, and defaults to disabled when no
row is present.
Because each environment has its own FeatureFlag table, the section is enabled
where it is wanted (local, staging, review environments) and left disabled in
sandbox and production, where no row is created. Operators who wonder why the
Emails navigation is or is not visible should check this flag in the target
environment.
Gateway endpoint
The list is served by GET /email-accounts/@me. It resolves the current broker
from the authenticated session and the tenant, and returns the broker's own
accounts plus every account shared within the broker's brokerage firm. The
response also supplies canManageSharedAccounts, which controls the shared
section's management actions, and connectedBy on each account.
Both add buttons use POST /email-accounts/connect with the selected provider,
the frontend return URL, and isShared. A Microsoft 365 shared mailbox adds
delegatedMailboxAddress; it is accepted with the outlook provider only and
rejected with DELEGATED_MAILBOX_REQUIRES_OUTLOOK otherwise. Reconnection uses
POST /email-accounts/:emailAccountId/reconnect and preserves the sharing mode;
disconnection uses DELETE /email-accounts/:emailAccountId. The server enforces
the permissions independently of the UI. These endpoints are internal and are
not part of the public partner API.
How connected messages are synchronized
You can receive message metadata before its body is available. Korint ingests mailbox events and retrieves bodies asynchronously; there is no public API operation to trigger body retrieval.
- A
messageNewevent ingests the message only when its account is active and has not been deleted. If account creation is still pending, processing fails so the background job can retry. - Ingestion stores message metadata and attachment metadata, with
bodyHtmlinitiallynullandbodyStatusset topending. Drafts are skipped; Outlook messages in draft, junk, or trash folders are also skipped. - Replaying an ingested message updates its read state without duplicating the
email or its attachments. When
bodyStatusispending, ingestion queues a body-fetch job with up to five attempts. - A
messageUpdatedevent changes a known email's read state only when it contains a read-state change. For an unknown email, Korint fetches its current metadata and runs ingestion, including the account and folder checks. A message that no longer exists at the provider is ignored. - Inactive or deleted accounts skip new-message and message-update processing.
A
messageDeletedevent leaves the stored email in Korint.
How message bodies become available
The internal FETCH_EMAIL_BODY command uses fetchMessageBody to retrieve the
message without marking it as read. It stores EmailEngine's web-safe HTML in
Email.bodyHtml, without embedding attached images. If only plain text is
available, Korint escapes HTML characters and wraps the text in <pre> tags.
A message with no body stores an empty string, which counts as a completed
retrieval.
bodyStatus | Meaning |
|---|---|
pending | The body is awaiting retrieval or a retry. |
stored | Retrieval succeeded, including an empty body. Stored bodies are not fetched again. |
unavailable | The provider returned HTTP 404: the message is gone. The job completes without retrying. |
failed | Retrieval failed on the last known attempt. |
Other retrieval errors are recorded in bodyError and propagated for retry;
bodyHtml remains null. Without retry metadata, an error is not classified as
the final attempt. A successful retrieval clears bodyError.
Message responses larger than 10 MiB are rejected through the same error path. Korint does not truncate the response or save a partial body: truncating markup before sanitization can turn a non-empty message into an empty body.
Reading an email
GET /emails/:emailId returns one stored email for the general inbox: its
addresses, bodyStatus and bodyHtml, and its attachments with their storage
status. The caller must be an authenticated broker, and the email must belong to
one of the broker's own accounts or to an account shared within the broker's
brokerage firm, the same rule as the list. Disconnected, errored and deleted
accounts keep their stored emails readable. An unknown or invisible email answers
HTTP 400, so the response does not reveal whether the email exists.
Opening an unread email marks it read in Korint and queues an
email.mark_as_seen job, five attempts with exponential backoff, that adds the
\Seen flag through EmailEngine; a message that no longer exists at the
provider counts as done. The job is queued before the local flag is written: a
failed request can leave a mailbox write without a local flip, which the next
messageUpdated event repairs, but never a local flip without a mailbox write.
There is no "mark as unread"; the mailbox remains the source of truth and later
flag changes are mirrored by the synchronization described above.
Each attachment carries status (pending, stored, failed, skipped) and
failureReason. A stored inline attachment also carries inlineUrl, a presigned
URL valid for five minutes that the frontend substitutes for the matching cid:
reference in the body. Regular attachments are downloaded through
GET /emails/:emailId/attachments/:emailAttachmentId/download-url. Like the
other endpoints of this capability, it is internal and not part of the public
partner API.
How emails are linked to policies and customers
Linking is decided per thread, not per message. When the body job reaches a
terminal bodyStatus (stored, unavailable or failed), the same
transaction records an analysis request for the message's thread in
EmailThreadAnalysis. The thread key is the provider's thread id, or the email
id when the provider gives none. Each request increments requestedVersion; a
linking run acknowledges only the version it read, so a message that arrives
during a run triggers another one. Replaying an already stored body creates a
missing request without fetching the body again.
The linking run and the manual attach and detach actions ship separately.
Their decisions are stored in EmailEntityLink, one row per email, entity and
decision:
status | Meaning |
|---|---|
candidate | Evidence for a link was observed, but the entity is not attached. |
linked | The entity is attached to the email. |
detached | The link is no longer active. |
reasons stores the observed evidence and is the only field updated in place.
A status change soft-deletes the active row and inserts a new one, so an email
has at most one active row per entity and its history stays queryable.
decidedByUserId and decidedAt identify a manual decision; the linking run
never writes a manual row.
Alerting the broker who connected the account
When a connected account stops syncing because its authorization has expired or
been revoked, EmailEngine reports an authenticationError and Korint marks the
account as error. On the transition into that state, the broker who connected the
account receives an email at their Korint login address inviting them to
reconnect the mailbox, with a link to the connected email accounts page.
The alert fires once per transition: EmailEngine re-emits the error on every
retry and webhook deliveries can be replayed, but a conditional write lets only
the job that actually flips the account to error send the email. The alert is
gated on the error status alone, so a disconnected account raises none:
EmailEngine reconnects a disconnected account on its own with backoff, so it
needs no broker action.
Like the rest of this capability, the alert is configured for the demo tenant only, and it is delivered through the internal SES transport.