1. Features
  2. Connect flows
Connect flows

Connect a mailbox in one step.

Send users to a hosted page, or keep them in your own interface while workwae runs the OAuth exchange. Gmail, Microsoft 365 in any of its clouds, and any IMAP server, with the credential sealed before anything is stored.

connect.ts
// One link: Google, Microsoft or any IMAP mailbox
const session = await workwae.connectSessions.create({
  redirectUri: "https://app.example.com/mail/connected",
  providers: ["google", "microsoft", "imap"],
  externalRef: user.id,
  loginHint: user.email,
  state: csrfToken,
  options: { calendarEnabled: true, contactsEnabled: true },
});

return redirect(session.url);
// The browser comes back to your redirect URI
// ?status=success&account_id=<id>&state=<your state>
// ?status=error&error=access_denied&state=<your state>
What you get

Every way to get a mailbox connected

The browser flows end the same way: the user returns to a redirect URI on your allowlist, and account.connected announces the account to your webhooks.

Hosted connect page

Google and Microsoft buttons, and an IMAP and SMTP form for any other mailbox. A link works once, for 60 seconds up to 24 hours (one hour by default), and can prefill the address the user should connect.

Headless OAuth

Your own provider picker. workwae returns the URL to send the user to, then handles the callback, the token exchange and the account, and returns to your redirect URI.

Refresh-token import

Already hold a refresh token from your own Google or Microsoft app? Hand it over and the account is connected without any user interaction.

Your OAuth apps or ours

Start on workwae's Google and Microsoft apps, then register your own to show your name on the consent screen and own the grant. Google apps can also push Gmail changes instead of being polled.

Every Microsoft cloud

Microsoft 365 worldwide (including GCC), GCC High, DoD and 21Vianet. Each sign-in and every call goes to the account's own cloud.

IMAP and SMTP

A password, or Google and Microsoft sign-in over XOAUTH2. Servers are found from the address, and the login is checked before anything is stored. CalDAV and CardDAV are found the same way.

Only the scopes you use

Calendar and contacts permissions are asked for only when those features are on for the account and your plan includes them. Shared mailbox permissions only when you ask.

Reconnects keep the account

A revoked grant moves the account to reauth_required with an event naming the reason. A reconnect link signs the same account in again, with the same id.

Bound to one browser

Every sign-in passes a Continue page that names your product, the provider and the account. Continuing binds it to that browser, so a forwarded link connects nothing.

How it works

What each connection asks for

workwae asks the provider for the least it needs, and checks the scopes granted, since users can untick permissions on Google's consent screen.

Connection OAuth app Permissions requested Calendar and contacts
Google, Gmail API workwae's or yours gmail.modify, plus calendar and contacts when those are on Google Calendar and Google Contacts
Microsoft 365, Graph workwae's, in the cloud you name, or yours, registered per cloud User.Read, Mail.ReadWrite, Mail.Send; Calendars.ReadWrite and MailboxSettings.Read with calendar; Contacts.ReadWrite with contacts; the .Shared variants for shared mailboxes Outlook calendars and contacts
Google over IMAP and SMTP Yours only https://mail.google.com/, which workwae's app never asks for; through it the start is refused with google_imap_requires_own_app Mail only
Microsoft over IMAP and SMTP workwae's or yours IMAP.AccessAsUser.All and SMTP.Send on the cloud's Exchange host, plus Graph User.Read to read the address. The mailbox must allow IMAP and SMTP AUTH. Mail only
IMAP and SMTP with a password None The address and password, checked by logging in to each server CalDAV and CardDAV, discovered or given

Microsoft clouds

Value Cloud
globalMicrosoft 365 worldwide, including GCC (moderate). The default.
gcc-highUS Government GCC High
dodUS Government DoD
chinaMicrosoft 365 operated by 21Vianet
  • Tokens do not cross clouds, so each cloud needs its own app registration. Your own app carries its cloud; microsoftCloud picks the cloud for workwae's app.
  • Personal Microsoft accounts (outlook.com) exist only in global.
  • In 21Vianet, Graph has no message delta yet, so mail runs over IMAP and SMTP with Microsoft sign-in.

IMAP server autodiscovery

Order Source
1A built-in table of known providers
2The domain's own autoconfig file
3The Mozilla ISPDB
4DNS SRV records (RFC 6186 and 8314)
5MX records: Google or Microsoft MX means that provider, with a hint to sign in with it
  • CalDAV and CardDAV are found from the provider table, /.well-known and DNS SRV, then resolved to the user's calendars and address books with the login.
  • Every lookup and mail server connection refuses private and loopback addresses, and plaintext logins are refused.
  • A stored password is never sent to a new server: changing a host or login needs the password in the same request.
Safeguards

Built so a link cannot be hijacked

A connect link grants access to a mailbox. These checks make sure it connects the right one, for the right person, in the right place.

  • A redirect allowlist

    Users return only to URIs on your allowlist (up to 50), matched on origin and path, with the query string yours to vary. Entries must be https, except for localhost.

  • A Continue page first

    Every OAuth sign-in, hosted or headless, opens on a page naming your organization, the provider and the account. Only its Continue button, accepted only from workwae's own origin, leads on to the provider.

  • One browser, once

    Continuing works once and binds the sign-in to that browser with an HttpOnly cookie. A callback arriving in any other browser gets an error page and connects nothing.

  • The address comes from the provider

    The mailbox address is asked of Google or Microsoft with the new grant, never read off the ID token, whose email claim another directory's administrator can set.

  • No takeover of existing accounts

    A link made without an account id refuses a mailbox you already have (account_exists). A reconnect must sign in with the same mailbox (account_mismatch).

  • Your API key stays on your server

    The hosted page calls the API with its session token only, rate limited per session. Tokens and passwords are sealed with keys held in Vault before they are stored.

Account status

Know when an account needs the user

Every account reports a status and a statusReason. Two are yours to set; two are set by the platform.

Status Set by What it means
active You, or a successful reconnect The account syncs and sends.
paused You Its sync stops until you set it back to active.
reauth_required workwae The provider refused the grant (the user revoked access, changed a password, or an administrator restricted the app), or an IMAP server refused the stored password. account.reauth_required names the reason, and calls that need the grant answer account_reauth_required. A reconnect clears it; for a password account, so does a new password.
disabled workwae The platform stopped the account. It cannot be set through the API.
Build it

Reconnect an account when its grant is revoked

Listen for account.reauth_required and send the user a reconnect link for the same account.

  • Same account. The session signs in to the account's own provider, cloud, OAuth app and mail access, and the account keeps its id.
  • Password accounts get the IMAP form, so the user can enter a new password; the stored servers stay.
  • On success the account becomes active again and account.connected arrives with reauthorized: true.
  • Headless works too: pass accountId to an OAuth start and the account's address becomes the sign-in hint.
  • No redirect URI? The user ends on a workwae confirmation page, and you read the outcome from the session.
reconnect.ts
// In your webhook handler
if (event.known && event.type === "account.reauth_required") {
  const { url } = await workwae.connectSessions.create({
    accountId: event.accountId,
    providers: ["google", "microsoft", "imap"],
    redirectUri: "https://app.example.com/mail/connected",
    ttlSeconds: 86_400,
  });
  // e.g. "invalid_grant"
  await emailUser(event.data.externalRef, url, event.data.reason);
}
// Without a redirect URI, follow the session
const s = await workwae.connectSessions.get({ sessionId });
// s.status: "pending" | "completed" | "failed"
if (s.status === "failed") console.warn(s.error);

// Everything still waiting on a user
const { items } = await workwae.accounts.list({
  status: "reauth_required",
});
Build it

Your own app, in a government cloud

Register your Microsoft app for its cloud once, then start headless sign-ins from your own interface.

  • Register the callback https://api.workwae.com/connect/callback/microsoft (or /google) with the provider.
  • authority is your directory id, or common for any directory.
  • authorizeUrl is the Continue page. Redirect the user's own browser to it.
  • Import instead: a refresh token your app already holds connects with accounts.create, no browser needed.
  • An app in use stays: it cannot be deleted while accounts still use it.
gcc-high.ts
const app = await workwae.oauthApps.create({
  provider: "microsoft",
  cloud: "gcc-high",
  name: "Example Mail",
  clientId: process.env.ENTRA_CLIENT_ID!,
  clientSecret: process.env.ENTRA_CLIENT_SECRET!,
  authority: process.env.ENTRA_DIRECTORY_ID!,
});

const { authorizeUrl } = await workwae.oauthAuthorizations.create({
  provider: "microsoft",
  oauthAppId: app.id,
  redirectUri: "https://app.example.com/mail/connected",
  externalRef: user.id,
  state: csrfToken,
  options: { calendarEnabled: true },
});
// Already hold a refresh token from your app?
const account = await workwae.accounts.create({
  provider: "microsoft",
  refreshToken,
  oauthAppId: app.id,
  externalRef: user.id,
});
Build it

Connect any IMAP mailbox

Look the servers up from the address, steer Google and Microsoft mailboxes to their own sign-in, and connect the rest with a password.

  • source says where the settings came from: provider, autoconfig, ispdb, srv or mx.
  • oauth names Google or Microsoft when their sign-in reaches the mailbox: prefer it over a password.
  • autodiscovery_failed is the cue to ask the user for the servers.
  • Login errors name the server that refused, so you can tell the user whether IMAP or SMTP needs another password.
  • Google over IMAP needs your own Google OAuth app and mailAccess: "imap"; Microsoft works with either app.
imap.ts
import { isApiError } from "@workwae/sdk";

try {
  const found = await workwae.autoconfig.lookup({ email });
  // Google or Microsoft hosts it: offer that sign-in
  if (found.oauth) return offerSignIn(found.oauth);
} catch (error) {
  if (!isApiError(error, "autodiscovery_failed")) throw error;
  return askForServers(email);
}

// Servers left out: found again, then checked by logging in
const account = await workwae.imapAccounts.create({
  email,
  password: appPassword,
  externalRef: user.id,
});

// Later: the server refused the password; the user sent a new one
await workwae.imapSettings.update({
  accountId: account.id,
  password: newPassword,
});
Outcomes

Why a connect can fail

A failed attempt returns to your redirect URI with status=error and one of these codes. Treat a code you do not know as a failure: new ones may be added.

Code Meaning
access_deniedThe user declined the consent screen.
scope_deniedThe user unticked a permission the account needs.
account_existsThe mailbox is already connected. Send a reconnect link for it instead.
account_mismatchA reconnect signed in with a different mailbox than the account being reconnected.
session_used, session_expiredThe link was already completed, or opened after it expired.
missing_refresh_tokenThe provider issued no refresh token. The user may need to remove the app's earlier grant first.
google_imap_requires_own_appGoogle over IMAP was asked for through workwae's app. Use your own Google OAuth app.
plan_limit_accountsYour plan's account limit is reached.

The full list is in the connect guide.

FAQ

Questions about connecting

Can the consent screen show our name instead of workwae's?

Yes. Register your own Google and Microsoft OAuth apps and pass their ids to a session or an OAuth start. Users then consent to your app, and you own the grant.

Do users have to grant calendar access if we only use mail?

No. Calendar and contacts permissions are asked for only when calendarEnabled or contactsEnabled is on for the account and your plan includes those modules.

What happens if someone forwards a connect link?

The person who opens it sees the Continue page naming your organization, the provider and the account. Continuing binds the sign-in to their browser and works once, so a callback in any other browser connects nothing.

Can an administrator connect a whole domain without each user signing in?

Yes, with an organization connection: Google Workspace domain-wide delegation or a Microsoft app with admin consent. See organization connections.

Can we move accounts from EmailEngine without asking users to sign in again?

Yes. The migration copies accounts with their refresh tokens, so they keep working with the same OAuth app. See migrating from EmailEngine.

Early access

Let users bring their own mailbox.

workwae is in early access. Tell us what you are building, and read the connect guide in the meantime.