Skip to main content

Hosted Authentication

EmailEngine's hosted authentication feature provides a user-friendly web interface for connecting email accounts via OAuth2. Instead of manually handling OAuth2 flows in your application, you can redirect users to EmailEngine's authentication forms where they complete the setup process.

Overview

What is Hosted Authentication?

Hosted authentication is EmailEngine's built-in web interface for account setup. It provides:

Pre-built OAuth2 flows:

  • Sign in with Google button
  • Sign in with Microsoft button
  • Automatic OAuth2 token management
  • User-friendly consent screens

Automatic account registration:

  • Creates account in EmailEngine
  • Stores OAuth2 tokens securely
  • Connects to IMAP/SMTP or API
  • Returns user to your application

No OAuth2 code required:

  • EmailEngine handles the OAuth2 exchange
  • Your app only generates a form URL
  • User completes authentication
  • EmailEngine redirects back with results

When to Use Hosted Authentication

Good use cases:

  • Quick integration - Get OAuth2 working in minutes
  • Standard flows - Gmail and Outlook OAuth2
  • User-facing setup - Let users connect their own accounts
  • No OAuth2 expertise - Don't want to build OAuth2 flows

Not suitable for:

  • Backend automation - Use direct API registration with tokens
  • Custom OAuth2 flows - Use authentication server instead
  • Headless systems - No user interaction available
Alternative Approaches

How It Works

Authentication Flow

Step-by-Step Process

  1. Your application calls EmailEngine API to generate form URL
  2. EmailEngine returns unique authentication URL
  3. Your application redirects user to this URL
  4. User sees EmailEngine's authentication form
  5. User clicks provider button (Google/Microsoft)
  6. Provider shows consent screen
  7. User grants permissions
  8. EmailEngine receives OAuth2 tokens
  9. EmailEngine creates and connects account
  10. EmailEngine redirects user back to your application

Generating Authentication Forms

Basic Form Generation

Generate a form URL for a user:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_EMAILENGINE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "john@gmail.com",
"name": "John Doe",
"redirectUrl": "https://myapp.com/settings"
}'

Response:

{
"url": "https://emailengine.example.com/accounts/new?data=eyJhY2NvdW50IjoidXNlcjEyMyIsImVtYWlsIjoiam9obkBnbWFpbC5jb20iLCJuYW1lIjoiSm9obiBEb2UiLCJyZWRpcmVjdFVybCI6Imh0dHBzOi8vbXlhcHAuY29tL3NldHRpbmdzIn0"
}

Direct the user to this URL to begin authentication. The URL is single-use and expires 24 hours after it was generated; a second visit, or a visit after expiry, gets an "Invalid or expired account setup URL" error page. Generate a new URL for every attempt.

Request Parameters

ParameterRequiredDescription
accountNoAccount ID. If not provided or null, a unique ID is generated automatically. If an existing account ID is provided, that account is re-authorized and its settings updated, see Re-authorizing an existing account
emailNoPre-fill email address on form
expectedEmailNoRestrict the form to a single address - setup is rejected if the user authenticates as someone else
nameNoPre-fill display name on form
typeNoPre-select the account type: "imap" or an OAuth2 application ID (skips the selection screen)
delegatedNoRegister the account as a shared mailbox. Microsoft 365 OAuth2 only
notifyFromNoOnly emit webhooks for messages received after this date. Defaults to the moment the account is created. IMAP only
subconnectionsNoFolders to watch on their own connection, for immediate notifications
pathNoRestrict which folders the account syncs at all
redirectUrlYesWhere to send user after completion

notifyFrom, subconnections, and path are applied to the account the form creates, which saves an update call after the redirect.

Skipping Account Type Selection

Use the type parameter to bypass the account type selection screen and send users directly to the authentication flow:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_EMAILENGINE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "john@gmail.com",
"type": "AAABhaBPHsc",
"redirectUrl": "https://myapp.com/settings"
}'

Values for type:

ValueEffect
"imap"Direct to manual IMAP/SMTP configuration form
OAuth2 App IDDirect to that provider's OAuth2 authorization page

The OAuth2 App ID (Provider ID) is visible in EmailEngine's Integrations > OAuth2 Apps settings page. This is EmailEngine's internal ID for the OAuth2 application, not the provider's client ID.

Better User Experience

Using the type parameter provides a smoother experience - users go directly to Google or Microsoft authorization without seeing an intermediate selection screen.

Implementation Example

const axios = require('axios');

async function generateAuthUrl(userId, userEmail, userName) {
const response = await axios.post(
'https://emailengine.example.com/v1/authentication/form',
{
account: userId,
email: userEmail,
name: userName,
redirectUrl: 'https://myapp.com/settings'
},
{
headers: {
'Authorization': 'Bearer YOUR_EMAILENGINE_TOKEN',
'Content-Type': 'application/json'
}
}
);

return response.data.url;
}

// Usage in Express route
app.get('/connect-email', async (req, res) => {
const authUrl = await generateAuthUrl(
req.user.id,
req.user.email,
req.user.name
);

res.redirect(authUrl);
});

Handling Redirects

Success Redirect

After successful authentication, EmailEngine redirects to your redirectUrl with query parameters:

https://myapp.com/settings?account=user123&state=new

Query Parameters:

ParameterDescription
accountThe account ID you provided
stateResult of the operation: new (account was created) or existing (existing account was updated)
Error Handling

If authentication fails (OAuth2 error, user cancellation, a rejected expectedEmail, etc.), EmailEngine displays an error page rather than redirecting to your redirectUrl. Your application only receives a redirect on successful authentication.

Where the user can fix the problem themselves, that page offers a way back into the flow, so most failures are resolved without returning to your application at all.

Account Initialization

The redirect happens immediately after authentication completes, but the account may still be initializing (syncing mailboxes, etc.). EmailEngine sends an accountInitialized webhook once the account is fully processed and ready to accept API calls. If you need to make API calls immediately after redirect, either wait for this webhook or poll the account status endpoint until the state is connected.

Handling the Redirect

app.get('/settings', async (req, res) => {
const { account, state } = req.query;

if (state === 'new') {
// New account was created
await db.users.update(
{ id: account },
{ emailConnected: true }
);

res.render('settings', {
message: 'Email account connected successfully!'
});
} else if (state === 'existing') {
// Existing account was updated
res.render('settings', {
message: 'Email account credentials updated successfully!'
});
}
});

Retry Flow

If authentication fails, users see an error page on EmailEngine. Where the user can fix the problem (a permission left unchecked on Google's consent screen, or signing in as the wrong account), that page carries a Try Again button that restarts the provider flow with a fresh single-use setup. For other failures, provide a "Connect Email" button on your settings page that generates a new authentication form URL, since the original URL cannot be reused.

Re-authorizing an existing account

Generating a form with the account ID of an existing account re-authorizes it: the new tokens or IMAP credentials replace the stored ones, everything else on the account is kept, and the redirect carries state=existing. Use this when a user's grant was revoked, when the account reports authenticationError, or when the OAuth2 application's scopes changed.

If the account was not operational at the time (an error state, or switched off by the safety net after repeated authentication failures), completing the form also requests a full reconnect, so syncing resumes without a separate reconnect call. Since v2.79.4 this includes an account that EmailEngine switched off itself: the one that reports unset with a non-null authFailureDisabledAt. See Accounts switched off after authentication failures.

The admin interface offers the same flow as the Re-authenticate button on an OAuth2 account's page.

Pre-filling Information

Email Address

Pre-fill the email address to streamline the process:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "john@gmail.com",
"redirectUrl": "https://myapp.com/settings"
}'

The authentication form will show this email address, and for Gmail/Outlook, it will be used as the login_hint parameter in the OAuth2 flow.

email is only a suggestion. The user can type a different address on the IMAP form, or pick a different account on the provider's consent screen, and the setup still completes. Use expectedEmail when the address has to be the one you specified.

Requiring a Specific Address

expectedEmail turns the address into a condition. If the user completes the form as someone else, the setup is rejected before any credentials are stored, so an existing account keeps working with the credentials it already had:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "john@gmail.com",
"expectedEmail": "john@gmail.com",
"redirectUrl": "https://myapp.com/settings"
}'

This matters most for reconnection. Without it, whoever opens the link decides which mailbox the account ends up pointing at - handy when a user is genuinely moving to a new mailbox, unwanted when the account belongs to someone in particular.

What the address is checked against depends on how the user authenticates:

Setup typeCompared against
Gmail, Outlook, Mail.ruThe address the provider reports for the account that signed in
IMAP/SMTPThe address entered on the form

For OAuth2 the provider vouches for the identity, so the check establishes who owns the mailbox. For IMAP it does not: the credentials are entered separately from the address, so a match confirms the account is registered under the expected address rather than proving the mailbox belongs to it.

A rejected setup does not return the user to your application. EmailEngine shows them a page naming both the expected address and the one they used, with a button to start the setup again - for OAuth2 that reopens the provider's account picker, so they can switch accounts without going back to you first.

The comparison is exact, apart from letter case and surrounding spaces. Providers that treat several spellings as one mailbox are not accounted for: Google ignores dots, accepts googlemail.com for gmail.com and strips +tags, so john.doe@gmail.com is rejected against an account that Google reports as johndoe@gmail.com. Pass the address exactly as the provider reports it - the rejection page names both addresses, which is what makes a near-miss like this visible.

The value is stored on the account, so it also applies to later links that omit it, and to the Re-authenticate button in the EmailEngine dashboard. To move an account to a different address, issue a new form link carrying the new expectedEmail - a link that states an address replaces the stored one. To drop the restriction entirely, clear the field with the update account API:

curl -X PUT https://emailengine.example.com/v1/account/user123 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"expectedEmail": null}'
note

The address is recorded when a setup completes, not when the link is generated. A link that was issued with expectedEmail but never used leaves nothing behind, so a later link for the same account ID is unrestricted. Put expectedEmail on the link that creates the account if you want the restriction to hold from the start.

Shared and delegated Microsoft 365 mailboxes are a special case: the user signs in with their own account to reach a mailbox belonging to someone else, so expectedEmail has to name the person signing in, not the shared mailbox address.

Display Name

Pre-fill the account name:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "john@gmail.com",
"name": "John Doe",
"redirectUrl": "https://myapp.com/settings"
}'

This name will be displayed in EmailEngine's account list.

Advanced Features

Delegated Access (Shared Mailboxes)

For Microsoft 365 shared mailboxes, include the delegated flag:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "shared-support",
"email": "support@company.com",
"delegated": true,
"redirectUrl": "https://myapp.com/settings"
}'

User will authenticate with their personal account but access the shared mailbox.

Learn more about shared mailboxes >

User Experience

What Users See

1. Account Type Selection

When multiple authentication options are available, users first choose their provider:

Account type selection form

2. IMAP/SMTP Configuration (if selected)

For manual IMAP setup, users enter their server credentials:

IMAP/SMTP configuration form

3. OAuth2 Consent Screen (if selected)

For OAuth2 providers, users are redirected to Google or Microsoft to grant permissions.

4. Redirect Back

After successful authentication, users are automatically redirected to your application's redirectUrl.

Language and Theme

The hosted pages accept two query arguments that let you match the form to the user's language and to your own application's color scheme. Append them to the URL returned by POST /v1/authentication/form - the URL already carries a query string, so use &:

https://emailengine.example.com/accounts/new?data=eyJ...&locale=fr&theme=dark

Language (locale):

Displays the form in a specific language instead of relying on browser negotiation. Supported values: en, de, fr, nl, et, pl, ja. The choice is stored in a cookie, so it persists through the multi-step setup flow. Without the argument, EmailEngine negotiates the language from the browser's Accept-Language header, falling back to the server-wide default locale.

Learn more about translations and language selection >

Theme (theme):

Forces the light or dark color scheme so the page matches the application the user is coming from. Supported values: light and dark. The choice is remembered for the rest of the browser session, so it survives the setup steps that follow. Without the argument, the pages follow the theme the visitor chose in the EmailEngine admin interface in that browser, if any, and otherwise the system preference (prefers-color-scheme).

Version Availability

The theme argument requires EmailEngine v2.73.0 or later. The locale argument works on all recent versions.

Customization Options

Page Branding:

SettingPurposeExample
templateHeaderHTML block appended to top of pageApp logo, instructions, welcome message
templateHtmlHeadCustom <head> contentCSS style overrides, custom fonts

These can be configured via:

  • Configuration > Branding page in the EmailEngine dashboard
  • Settings API (POST /v1/settings)

Example - Add custom header with logo:

<div style="text-align: center; padding: 20px;">
<img src="https://your-app.com/logo.png" alt="Your App" height="40">
<p>Connect your email account to get started</p>
</div>

Example - Add custom CSS:

<style>
:root {
--ee-primary: #0057b8; /* buttons and links */
--ee-page-background: #f4f4f4; /* backdrop behind the page card */
}
.ee-card {
border-radius: 12px;
}
</style>
Framework-Free Styling

Since EmailEngine v2.73.0 the hosted pages are framework-free: plain HTML styled by a single standalone stylesheet with stable, human-readable class names (ee-card, ee-btn, ee-input, and so on) and CSS custom properties for the design tokens (colors, radii, shadows). Redefine the tokens in templateHtmlHead for quick restyling - your CSS loads after EmailEngine's, so it always wins - or target the ee-* classes directly for deeper changes. The stylesheet source (static/css/public.css in the EmailEngine repository) documents every token and class. Versions up to v2.72.x used Bootstrap 4 classes (btn-primary, card) instead.

OAuth2 provider settings (configured in Google Cloud Console / Azure AD):

  • App name displayed in consent screen
  • App logo
  • Privacy policy and terms of service links

See Also