Skip to content
Back to top
View this page

# OAuth2 Authentication (XOAUTH2)

aiosmtplib supports OAuth2 authentication via the XOAUTH2 mechanism, which is used by Gmail, Outlook.com, and other providers that have deprecated traditional password authentication.

The SMTP server must advertise `XOAUTH2` as a supported authentication method.

To use OAuth2, pass an async callable that returns a valid access token to the `oauth_token_generator` parameter.

Note

The `oauth_token_generator` parameter is mutually exclusive with `password`. You cannot use both at the same time.

## Basic Structure

    import aiosmtplib

    async def get_access_token() -> str:
        # Your token refresh logic here
        return "your_access_token"

    await aiosmtplib.send(
        message,
        hostname="smtp.gmail.com",
        port=465,
        use_tls=True,
        username="your.email@gmail.com",
        oauth_token_generator=get_access_token,
    )

## Token Expiry and Refresh

OAuth2 access tokens are short-lived (typically 1 hour). The callable passed as `oauth_token_generator` is responsible for returning a valid, non-expired token each time it is called. It will be called immediately before the `XOAUTH2` authentication command is sent to the server.

Your generator must handle expiry

aiosmtplib does not track token expiry times or automatically retry on authentication failure. Your callable should check if the token is expired and refresh it before returning. Some libraries (e.g. `google-auth`, documented below) will handle this.

## Gmail Example

Gmail allows OAuth2 for SMTP access. You’ll need to create a project in the Google Cloud Console, and obtain OAuth2 credentials.

The `google-auth` library provides credential management with automatic token refresh.

    import asyncio
    from email.message import EmailMessage

    import aiosmtplib
    from google.oauth2.credentials import Credentials
    from google.auth.transport.requests import Request

    # Load your credentials (obtained via OAuth flow)
    credentials = Credentials(
        token="ya29.access_token",
        refresh_token="1//refresh_token",
        token_uri="https://oauth2.googleapis.com/token",
        client_id="your_client_id.apps.googleusercontent.com",
        client_secret="your_client_secret",
    )

    async def get_gmail_token() -> str:
        """Return a valid access token, refreshing if needed."""
        if not credentials.valid:
            # google-auth is synchronous, so run in a thread
            await asyncio.to_thread(credentials.refresh, Request())
        return credentials.token

    async def send_email():
        message = EmailMessage()
        message["From"] = "your.email@gmail.com"
        message["To"] = "recipient@example.com"
        message["Subject"] = "Hello from aiosmtplib!"
        message.set_content("Sent using OAuth2 authentication.")

        await aiosmtplib.send(
            message,
            hostname="smtp.gmail.com",
            port=465,
            use_tls=True,
            username="your.email@gmail.com",
            oauth_token_generator=get_gmail_token,
        )

    asyncio.run(send_email())

## Outlook / Microsoft 365 Example

Microsoft requires OAuth2 for SMTP authentication. See the official Microsoft documentation for setup instructions.

The `msal` library provides credential management with automatic token refresh.

    import asyncio
    from email.message import EmailMessage

    import aiosmtplib
    import msal

    # Your Azure AD app registration details
    CLIENT_ID = "your_client_id"
    TENANT_ID = "your_tenant_id"  # Or "common" for multi-tenant apps

    # Scope for SMTP sending
    SCOPES = ["https://outlook.office.com/SMTP.Send"]

    # Create MSAL public client app (for user authentication)
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    )

    async def get_outlook_token() -> str:
        """Return a valid access token, refreshing silently if possible."""
        accounts = app.get_accounts()
        if accounts:
            # Try to get token silently (uses cached/refresh token)
            result = await asyncio.to_thread(
                app.acquire_token_silent, SCOPES, account=accounts[0]
            )
            if result and "access_token" in result:
                return result["access_token"]

        raise ValueError("No valid token available. Re-authentication required.")

    async def send_email():
        message = EmailMessage()
        message["From"] = "your.email@outlook.com"
        message["To"] = "recipient@example.com"
        message["Subject"] = "Hello from aiosmtplib!"
        message.set_content("Sent using OAuth2 authentication.")

        await aiosmtplib.send(
            message,
            hostname="smtp.office365.com",
            port=587,
            start_tls=True,
            username="your.email@outlook.com",
            oauth_token_generator=get_outlook_token,
        )

    asyncio.run(send_email())