Google Workspace delegation
Upload a service account key. workwae checks and seals it, then returns the client id and the exact scopes for the Workspace admin to authorize under Security, API controls, Domain-wide delegation.
A Google Workspace or Microsoft 365 administrator authorizes workwae once. You list the directory, add mailboxes by address, and each one becomes an account with the same mail, calendar and contacts API as an account a user connected.
// Your own Entra app, signed with a certificate
const connection = await workwae.orgConnections.create({
kind: "microsoft_app",
name: "Contoso",
cloud: "global",
credential: {
type: "certificate",
clientId,
certificate,
privateKey,
},
directoryId,
});
// Request a token the way accounts will
const { checks } = await workwae.orgConnections.test({
connectionId: connection.id,
});
// Pick mailboxes from the directory
const page = await workwae.orgConnections.directoryEntries({
connectionId: connection.id,
query: "sales",
});
One API for Google Workspace domain-wide delegation, Microsoft 365 app-only access and shared mailboxes. Accounts added this way sync and send like any other.
Upload a service account key. workwae checks and seals it, then returns the client id and the exact scopes for the Workspace admin to authorize under Security, API controls, Domain-wide delegation.
Use workwae's app with admin consent, or your own app registration with a client secret or a certificate and its PKCS#8 private key.
A Microsoft connection names its cloud: worldwide (including GCC), GCC High, DoD or 21Vianet. Sign-in and Graph calls stay in that cloud.
Page through the organization's users, up to 200 at a time, matching the start of names and addresses. Each entry shows the account already added for it, if any.
Up to 100 addresses per call. Each mailbox is checked through the connection first,
and you get one result per address: created,
exists or failed with the
reason.
Testing requests a token the way accounts will and reports each check, required or optional, so an administrator sees which scope or permission still needs granting.
Open a Microsoft 365 shared mailbox as an account of its own, through the grant of a user who can already open it, over Graph or Exchange IMAP.
When an administrator withdraws authorization, the affected accounts stop with
consent_revoked. Restore it, test the connection, and
they resume on their own.
With workwae's Microsoft app, the directory is taken from the administrator's own sign-in and recorded only for the console session that started it. A directory belongs to one such connection.
What each kind of access asks the administrator for, how workwae reaches the mailbox, and what happens when authorization is withdrawn.
| Google Workspace delegation | Microsoft 365 app-only | Microsoft 365 shared mailbox | |
|---|---|---|---|
| Who authorizes | A Workspace admin authorizes the service account's client id for the connection's scopes in the Admin console. | A directory administrator grants admin consent to the app. | A user who can already open the mailbox connects with shared mailbox access turned on (options.sharedMailboxesEnabled). |
| Credential | The service account key, sealed. Each token is a signed assertion for one mailbox. | workwae's app for the cloud, or your own app registration with a client secret or a certificate. | The delegating user's own grant; the shared mailbox holds none of its own. |
| Scopes and permissions | gmail.modify always; calendar by default; contacts when you choose; admin.directory.user.readonly with a directory admin. |
Required: Mail.ReadWrite, Mail.Send. Optional: Calendars.ReadWrite, MailboxSettings.Read, Contacts.ReadWrite, User.Read.All. |
Mail.ReadWrite.Shared and Mail.Send.Shared, plus Calendars.ReadWrite.Shared and Contacts.ReadWrite.Shared for calendar and contacts. |
| Directory | The Admin SDK user list, read as the directory admin you name, only when you ask. | Graph's user list, with User.Read.All. |
Not applicable |
| How the mailbox is reached | Gmail, Calendar and People work unchanged, as for any Google account. | Graph, addressed as /users/{mailbox}, change notification subscriptions included. |
Graph as /users/{mailbox}, or Exchange IMAP logged in as user@domain\shared. |
| When access is withdrawn | Google refuses the token: the accounts move to reauth_required with consent_revoked. |
A token without Mail.ReadWrite means consent was withdrawn: consent_revoked. |
The delegating account is deleted: reauth_required with delegate_missing. |
pending_consent until a Microsoft directory consents,
unverified after a credential or scope change, then
active or failing with the
failed check as its detail.
A missing optional permission limits only calendar, contacts or the directory. An account without contacts access reports what to grant, and its mail keeps syncing.
Every Google mailbox token asks for all of the connection's scopes, so the admin must authorize each one the connection lists, or no token is issued and mail stops too.
Accounts added through a connection can't be taken over by a user sign-in, and a connection can't be deleted while live accounts still use it.
Tokens don't cross clouds, and each cloud needs its own app registration. Every Microsoft connection and account records its cloud, and workwae signs in and calls Graph there.
| Cloud | Covers | Sign-in | Graph |
|---|---|---|---|
global |
Worldwide Microsoft 365, including GCC | login.microsoftonline.com |
graph.microsoft.com |
gcc-high |
US Government GCC High | login.microsoftonline.us |
graph.microsoft.us |
dod |
US Government DoD | login.microsoftonline.us |
dod-graph.microsoft.us |
china |
Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn |
microsoftgraph.chinacloudapi.cn |
Register the service account key, hand the admin the client id and scopes, and test once they're authorized.
user you name.
const connection = await workwae.orgConnections.create({
kind: "google_delegation",
name: "Acme Workspace",
domain: "acme.com",
serviceAccountKey, // the JSON key file
calendarEnabled: true,
contactsEnabled: true,
directoryAdmin: "it-admin@acme.com",
});
// For the admin: Security > API controls > Domain-wide delegation
showAdmin(connection.clientId, connection.google?.scopes);
// After they authorize it, test as the directory admin
const result = await workwae.orgConnections.test({
connectionId: connection.id,
});
for (const check of result.checks) {
if (!check.isOk) console.warn(check.name, check.detail);
}
Walk the directory, skip mailboxes that are already accounts, and add the rest. Every address gets its own result, in request order.
externalRef per mailbox links
the account to your records.
import { paginate } from "@workwae/sdk";
const listDirectory = workwae.orgConnections.directoryEntries;
const query = { connectionId, query: "support" };
const mailboxes = [];
for await (const entry of paginate(listDirectory, query)) {
if (entry.accountId) continue; // already an account
const { email, id: externalRef } = entry;
mailboxes.push({ email, externalRef });
}
const { items } = await workwae.orgConnections.addAccounts({
connectionId,
mailboxes,
});
for (const added of items) {
if (added.status !== "failed") continue;
// e.g. invalid_grant (no such user) or consent_revoked
console.warn(added.email, added.error?.reason);
}
With your own app registration, send the administrator to a consent link and get them back to your app. Shared mailboxes need only a user's grant.
status,
connection_id, error and your state. The
consenting directory is recorded and the connection tested.
// Your own app registration, in GCC High
const connection = await workwae.orgConnections.create({
kind: "microsoft_app",
name: "Contoso (GCC High)",
cloud: "gcc-high",
credential: { type: "client_secret", clientId, clientSecret },
});
// Send the administrator here
const { consentUrl } = await workwae.orgConnections.consent({
connectionId: connection.id,
redirectUri: "https://app.example.com/admin/consent-done",
state: tenantRef,
});
// A shared mailbox, through a user's own grant
const billing = await workwae.sharedMailboxes.create({
accountId: userAccountId,
email: "billing@contoso.us",
});
Accounts added through a connection announce the same account events as any other, as signed webhooks and in the change feed.
| Event | When |
|---|---|
account. |
A mailbox became an account. After a passing test brings stopped accounts back, it arrives again with reauthorized: true. |
account. |
The connection's authorization was refused for the account; reason carries the provider's refusal. |
account. |
The first sync of a new account finished, with its mailbox and message counts. |
account. |
An account's status, settings or other fields changed. |
No. Once an administrator has authorized the connection, you add mailboxes by address. Each one is checked through the connection before its account is created.
The affected accounts move to reauth_required with
consent_revoked, and the next test marks the connection
failing. Once access is restored, a passing test
returns those accounts to active and announces them again.
No. Accounts added through a connection belong to it, so a user sign-in can't re-authorize them. Fix the connection, then test it.
Gmail has no delegated access to another user's mailbox through its API. Add such mailboxes through a Google Workspace domain-wide delegation connection.
workwae's app is shared by every customer, so the directory is recorded only from the administrator's own sign-in and only for the console session that started it. A consent link forwarded to another organization's administrator attaches nothing. With your own app registration, you can start consent through the API.
Yes, once no live account uses it. Delete its accounts first; while any remain, the
delete is refused with connection_in_use.
Shared inboxes as accounts, with filtered webhooks on new mail and replies from the team address.
Connect the whole sales team at once and log every rep's email to the right deal.
Hosted and headless sign-in for the mailboxes that connect one user at a time.
Sync, search, send and track on every account a connection adds.
workwae is in early access. Tell us what you are building, and read the docs in the meantime.