Skip to content

Outlook / Microsoft 365 ​

kotlin
implementation("dev.reapermaga.mailkt:outlook:0.1.0")

The Outlook module uses MSAL4J as a confidential client for Exchange Online IMAP and SMTP.

Register the app in Microsoft Entra ​

  1. In the Entra admin center, go to App registrations → New registration.
  2. Choose the supported account types. Multi-tenant plus personal accounts matches the default common authority.
  3. Add a Web platform redirect URI for each exact callback route.
  4. Under Certificates & secrets, create a client secret for the backend.
  5. Under API permissions, add the delegated Office 365 Exchange Online permissions IMAP.AccessAsUser.All and, if you send mail, SMTP.Send.

Tenant settings

IMAP and authenticated SMTP must be enabled for the mailbox in Exchange Online. Organizations often disable SMTP AUTH by default.

Configure ​

kotlin
fun outlookClient(tokens: InMemoryTokenStore, sessions: InMemoryAuthorizationSessionStore): Outlook {
    val config = OutlookConfig(
        clientId = System.getenv("OUTLOOK_CLIENT_ID"),
        clientSecret = System.getenv("OUTLOOK_CLIENT_SECRET"), // held by the backend only
        allowedRedirectUris = setOf(URI("https://app.example.com/oauth/outlook/callback")),
        enableSending = true, // adds the SMTP.Send scope
    )
    return Outlook(config, tokens, sessions)
}
PropertyDefaultDescription
clientIdApplication (client) ID.
clientSecretClient secret, held by the backend only.
allowedRedirectUrisExact callback URIs registered in Entra.
authorityhttps://login.microsoftonline.com/common/Use a tenant-specific authority to restrict sign-in to one organization. Must be HTTPS.
enableSendingfalseAdds the SMTP.Send scope and the SMTP endpoint. Without it mailbox.outbox is null.

Requested scopes are https://outlook.office.com/IMAP.AccessAsUser.All, plus https://outlook.office.com/SMTP.Send when sending is enabled. MSAL4J adds offline_access for refresh tokens.

Authorize and open ​

kotlin
suspend fun outlookAuthorizeAndOpen(outlook: Outlook, email: String, code: String, state: String): Mailbox {
    // 1. The backend creates the browser URL for the frontend.
    val request = outlook.beginAuthorization(MailAddress(email), URI("https://app.example.com/oauth/outlook/callback"))
    println("Send the browser to ${request.authorizationUrl}")
    // 2. The provider redirects to the backend callback, which completes the flow.
    outlook.completeAuthorization(AuthorizationCallback(code = code, state = state))
    // 3. Open the mailbox using the stored tokens.
    return outlook.open(MailAddress(email))
}

In a real backend, the three steps are split across the begin endpoint and the callback route exactly as for Gmail.

Endpoints ​

ProtocolHostPortSecurity
IMAPoutlook.office365.com993Implicit TLS, XOAUTH2
SMTPsmtp.office365.com587STARTTLS, XOAUTH2

Exchange Online stores a copy of sent mail itself, so send(saveToSent = true) never appends a duplicate to Sent.

Released under the MIT License.