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.
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.
// 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>
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.
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.
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.
Already hold a refresh token from your own Google or Microsoft app? Hand it over and the account is connected without any user interaction.
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.
Microsoft 365 worldwide (including GCC), GCC High, DoD and 21Vianet. Each sign-in and every call goes to the account's own cloud.
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.
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.
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.
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.
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 |
| Value | Cloud |
|---|---|
global | Microsoft 365 worldwide, including GCC (moderate). The default. |
gcc-high | US Government GCC High |
dod | US Government DoD |
china | Microsoft 365 operated by 21Vianet |
microsoftCloud picks the cloud for workwae's app.global.| Order | Source |
|---|---|
| 1 | A built-in table of known providers |
| 2 | The domain's own autoconfig file |
| 3 | The Mozilla ISPDB |
| 4 | DNS SRV records (RFC 6186 and 8314) |
| 5 | MX records: Google or Microsoft MX means that provider, with a hint to sign in with it |
/.well-known and DNS SRV, then resolved to the user's calendars and address books with the login.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.
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.
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.
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 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.
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).
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.
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. |
Listen for account.reauth_required and send the user a
reconnect link for the same account.
account.connected arrives with reauthorized: true.
accountId to an OAuth start and
the account's address becomes the sign-in hint.
// 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",
});
Register your Microsoft app for its cloud once, then start headless sign-ins from your own interface.
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.
accounts.create, no browser needed.
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,
});
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.
mailAccess: "imap"; Microsoft works with either app.
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,
});
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_ | The user declined the consent screen. |
scope_ | The user unticked a permission the account needs. |
account_ | The mailbox is already connected. Send a reconnect link for it instead. |
account_ | A reconnect signed in with a different mailbox than the account being reconnected. |
session_, session_ | The link was already completed, or opened after it expired. |
missing_ | The provider issued no refresh token. The user may need to remove the app's earlier grant first. |
google_ | Google over IMAP was asked for through workwae's app. Use your own Google OAuth app. |
plan_ | Your plan's account limit is reached. |
The full list is in the connect guide.
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.
No. Calendar and contacts permissions are asked for only when calendarEnabled or contactsEnabled is on for the account and your plan includes those modules.
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.
Yes, with an organization connection: Google Workspace domain-wide delegation or a Microsoft app with admin consent. See organization connections.
Yes. The migration copies accounts with their refresh tokens, so they keep working with the same OAuth app. See migrating from EmailEngine.
Let every rep connect their own mailbox and calendar from inside your CRM.
Recruiters connect once; candidate threads and interviews follow.
Connect calendars on any provider and book on real availability.
Domain-wide delegation and app-only access, without a sign-in per user.
Hear about account.connected and account.reauth_required as they happen.
Sync, search and send from every connected mailbox through one API.
workwae is in early access. Tell us what you are building, and read the connect guide in the meantime.