Office 365 / Outlook / Hotmail
Which Setup Should I Use?
Section titled “Which Setup Should I Use?”Choose Personal if you send from a consumer Microsoft mailbox such as
@outlook.com, @hotmail.com, @live.com, or @msn.com. This path uses a
one-time sign-in to obtain a refresh_token, which Apprise then rotates
automatically during normal use.
Choose Organizational if you send from a work or school Microsoft 365
tenant backed by Exchange Online. This path uses an Azure app registration plus
the client_credentials flow with your tenant_id, client_id, and
client_secret.
Personal consumer accounts — @outlook.com, @hotmail.com, @live.com,
@msn.com, and regional Hotmail/Live variants — use a refresh token flow
instead of the organizational client credentials flow.
This involves two phases:
- One-time setup: register a public-client app in Azure, then run a short
script to sign in and capture your
refresh_token. - Ongoing: Apprise uses the stored
refresh_tokento obtain short-lived access tokens automatically. Every successful send silently rotates the refresh token, so it stays alive as long as Apprise is used at least once every 90 days.
App Registration
Section titled “App Registration”-
Go to App Registrations in the Azure portal and click Register an application.
-
Give it a name (e.g.
Apprise Personal). -
Under Supported account types, select Personal Microsoft accounts only.
-
Under Redirect URI, choose Public client / native (mobile & desktop) and set the URI to:
https://login.microsoftonline.com/common/oauth2/nativeclient -
Click Register.
-
-
From the Overview panel, copy the Application (client) ID - this is your
client_id. -
Under Manage -> Authentication, scroll to Advanced settings and set Allow public client flows to Yes, then click Save. This enables the device code flow used to obtain the initial token.
-
Under Manage -> API permissions:
- Click Add a permission -> Microsoft Graph -> Delegated Permissions
- Search for and check Mail.Send, then click Add permissions
Getting Your Refresh Token
Section titled “Getting Your Refresh Token”This is a one-time step. The scripts below use Microsoft’s
Device Code Flow:
your script prints a short code and a link; you open that link in any browser
and sign in; the script captures your refresh_token automatically.
The whole process typically takes under two minutes.
Only requests is required - already installed if you have Apprise.
Edit the CLIENT_ID line, then copy and paste the entire block into your
terminal.
import time, requests
# - Edit this lineCLIENT_ID = "your-client-id-here" # Application (client) ID from Azure# -
DEVICE_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"TOKEN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"SCOPE = "Mail.Send offline_access"
# Step 1 - request a device coder = requests.post(DEVICE_URL, data={"client_id": CLIENT_ID, "scope": SCOPE})r.raise_for_status()d = r.json()
# Step 2 - the script prints a link and a short code; open the link in a# browser and enter the code to sign in with your Microsoft accountprint("\n" + d["message"] + "\n")
# Step 3 - poll quietly until you finish signing in (usually under 60 sec)interval = d.get("interval", 5)while True: time.sleep(interval) r = requests.post(TOKEN_URL, data={ "client_id": CLIENT_ID, "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": d["device_code"], }) t = r.json() if "refresh_token" in t: print("Your refresh_token (use this in your Apprise URL):\n") print(t["refresh_token"]) break if t.get("error") == "slow_down": interval += 5 elif t.get("error") != "authorization_pending": print("Error:", t.get("error_description", t)) breakNo external tools beyond curl are needed. Run the steps below in the
same terminal session so the variables carry over between steps.
Step 1 - Set your client ID and request a device code
# - Edit this line, then paste the whole blockCLIENT_ID="your-client-id-here" # Application (client) ID from Azure# -
curl -s -X POST \ "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode" \ --data "client_id=${CLIENT_ID}&scope=Mail.Send+offline_access"The response is JSON. Look for two values:
user_code- the short code you type in the browser (e.g.ABCD-EFGH)device_code- the long opaque string you paste into Step 3 below
Step 2 - Sign in at Microsoft
Open microsoft.com/devicelogin in a
browser and enter the
user_code from the JSON above. Sign in with your Hotmail / Outlook / Live
account and approve the requested permissions.
Step 3 - Paste the device code and retrieve your refresh token
# - Edit this line with the device_code value from Step 1DEVICE_CODE="paste-device_code-here" # long string from the JSON above# -
# Run this after completing Step 2.# If the response shows "authorization_pending", wait a few seconds and# run it again - or use the auto-poll loop in the tip below.curl -s -X POST \ "https://login.microsoftonline.com/consumers/oauth2/v2.0/token" \ --data "client_id=${CLIENT_ID}&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=${DEVICE_CODE}"Look for "refresh_token" in the JSON response — that value is what goes into
your Apprise URL.
Works on Windows, macOS, and Linux. No extra modules needed -
Invoke-RestMethod is built in.
Edit the $ClientId line, then copy and paste the entire block into a
PowerShell terminal.
# - Edit this line$ClientId = "your-client-id-here" # Application (client) ID from Azure# -
$Scope = "Mail.Send offline_access"$DeviceUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"$TokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
# Step 1 - request a device code$DeviceResp = Invoke-RestMethod -Method Post -Uri $DeviceUrl ` -Body @{ client_id = $ClientId; scope = $Scope }
# Step 2 - the script prints a link and a short code; open the link in a# browser and enter the code to sign in with your Microsoft accountWrite-Host ""Write-Host $DeviceResp.messageWrite-Host ""
# Step 3 - poll quietly until you finish signing in (usually under 60 sec)$DeviceCode = $DeviceResp.device_code$Interval = $DeviceResp.interval
do { Start-Sleep -Seconds $Interval try { $TokenResp = Invoke-RestMethod -Method Post -Uri $TokenUrl ` -Body @{ client_id = $ClientId grant_type = "urn:ietf:params:oauth:grant-type:device_code" device_code = $DeviceCode } } catch { $TokenResp = $_.ErrorDetails.Message | ConvertFrom-Json if ($TokenResp.error -eq "slow_down") { $Interval += 5 } }} until ($TokenResp.refresh_token -or $TokenResp.error -notin @("authorization_pending", "slow_down"))
if ($TokenResp.refresh_token) { Write-Host "Your refresh_token (use this in your Apprise URL):`n" Write-Host $TokenResp.refresh_token} else { Write-Host "Error: $($TokenResp.error_description)"}Personal Syntax
Section titled “Personal Syntax”Valid syntax is as follows (both o365:// and azure:// are accepted aliases):
o365://{source}/{client_id}/{refresh_token}/o365://{source}/{client_id}/{refresh_token}/{targets}
Apprise automatically detects personal mode when source is a known consumer
domain (outlook.com, hotmail.com, live.com, msn.com, and regional
variants). Use ?mode=personal to force personal mode on any other domain.
Personal Parameters
Section titled “Personal Parameters”| Variable | Required | Description |
|---|---|---|
| source | *Yes | Your consumer email address (e.g. user@outlook.com). Used as the sender and, when no targets are given, also as the recipient. |
| client_id | *Yes | The Application (client) ID from your Azure app registration. |
| refresh_token | *Yes | The refresh token from the one-time setup above. Apprise rotates this automatically on each successful send. |
| targets | No | One or more recipient email addresses. If omitted, email is sent to source. |
| to | No | Alias for targets. Useful in YAML configuration. |
| cc | No | One or more Carbon Copy addresses. Accepts comma-separated values and named addresses (e.g. Name <email@example.com>). |
| bcc | No | One or more Blind Carbon Copy addresses. Accepts comma-separated values and named addresses (e.g. Name <email@example.com>). |
| reply_to | No | One or more Reply-To addresses. When a recipient replies, their reply goes here instead of to source. Accepts comma-separated values and named addresses. |
| mode | No | Force personal mode on a non-standard domain: ?mode=personal. |
Global Parameters
Section titled “Global Parameters”| Variable | Description |
|---|---|
| overflow | This parameter can be set to either split, truncate, or upstream. This determines how Apprise delivers the message you pass it. By default this is set to upstream 👉 upstream: Do nothing at all; pass the message exactly as you received it to the service.👉 truncate: Ensure that the message will fit within the service’s documented upstream message limit. If more information was passed then the defined limit, the overhead information is truncated.👉 split: similar to truncate except if the message doesn’t fit within the service’s documented upstream message limit, it is split into smaller chunks and they are all delivered sequentially there-after. |
| format | This parameter can be set to either text, html, or markdown. Some services support the ability to post content by several different means. The default of this varies (it can be one of the 3 mentioned at any time depending on which service you choose). You can optionally force this setting to stray from the defaults if you wish. If the service doesn’t support different types of transmission formats, then this field is ignored. |
| verify | External requests made to secure locations (such as through the use of https) will have certificates associated with them. By default, Apprise will verify that these certificates are valid; if they are not then no notification will be sent to the source. In some occasions, a user might not have a certificate authority to verify the key against or they trust the source; in this case you will want to set this flag to no. By default it is set to yes. |
| cto | This stands for Socket Connect Timeout. This is the number of seconds Requests will wait for your client to establish a connection to a remote machine (corresponding to the connect()) call on the socket. The default value is 4.0 seconds. |
| rto | This stands for Socket Read Timeout. This is the number of seconds the client will wait for the server to send a response. The default value is 4.0 seconds. |
| emojis | Enable Emoji support (such as providing :+1: would translate to 👍). By default this is set to no. Note: Depending on server side settings, the administrator has the power to disable emoji support at a global level; but default this is not the case. |
| tz | Identify the IANA Time Zone Database you wish to operate as. By default this is detected based on the configuration the server hosting Apprise is running on. You can set this to things like America/Toronto, or any other properly formated Timezone describing your area. |
Personal Examples
Section titled “Personal Examples”Send from a personal Outlook account (mode is auto-detected from the domain):
# source: you@outlook.com# client_id: aa-bb-cc-dd-ee# refresh_token: (from the script above)apprise -vv -t "Test Title" -b "Test Body" \ "o365://you@outlook.com/aa-bb-cc-dd-ee/your-refresh-token-here/"Send to a different recipient from your Hotmail account:
apprise -vv -t "Hello" -b "Message body" \ "o365://me@hotmail.com/aa-bb-cc-dd-ee/your-refresh-token-here/friend@example.com"Force personal mode on a non-standard domain:
apprise -vv -t "Hello" -b "Message body" \ "o365://me@custom.com/aa-bb-cc-dd-ee/your-refresh-token-here/?mode=personal"Account Setup
Section titled “Account Setup”Because Microsoft has disabled Basic Authentication (Username/Password), you must register an application in Azure to generate the credentials Apprise needs (Client ID, Secret, etc).
-
From the Azure Portal go to App Registrations (alt link).
- Use the search bar at the top of the portal and type
App Registrations. - If you still cannot access anything, your organization may restrict this.
You may need to contact your administrator to proceed.

- Use the search bar at the top of the portal and type
-
Click Register an application.
Warning: screen you may see if you do not have an Azure account
You must have an Azure account (specifically a subscription) for this area to work. Subscriptions are free but still require a credit card on file. To create a subscription:

Go to: Azure Portal -> Subscriptions
Click Add
Choose Azure subscription (Free)
No resources need to be deployed. This simply completes tenant provisioning.
After this, ensure you are viewing the correct directory:
- Click your avatar (top right)
Select Switch directory
- Choose the directory where the subscription was created.
-
In the registration form:
- Give it a name (for example:
Apprise Notifications). - Crucial: Select the 3rd option: Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts.
- Click Register.
- Give it a name (for example:
-
From the Overview panel you can retrieve:
- The Application (client) ID -> your
client_id - The Object ID -> your
source(when using an Object ID instead of an email address) - The Directory (tenant) ID -> your
tenant_id
- The Application (client) ID -> your
-
To create your
client_secret, expand Manage on the left:- Click Certificates & secrets -> Client secrets
- Click New client secret
- Provide a description (for example
Apprise Secret) and an expiry - Click Add
-
To set up permissions, expand Manage on the left:
- Click API permissions. You will likely already have User.Read as a default; we need to add more.
- Click Add a permission -> Microsoft Graph -> Application Permissions
- Search for and check Mail.Send, then click Add permissions
- Also add:
- User.Read.All - allows Apprise to resolve your Object ID when used
as
source - Mail.ReadWrite (optional) - required only for large attachments (> 3 MB)
- User.Read.All - allows Apprise to resolve your Object ID when used
as
Important: After adding, click Grant admin consent for <Directory Name> for the permissions to take effect. This button is next to Add a permission.
- You are ready. 🙂
Organizational Syntax
Section titled “Organizational Syntax”Valid syntax is as follows (both o365:// and azure:// are accepted aliases):
o365://{source}/{tenant_id}/{client_id}/{client_secret}/o365://{source}/{tenant_id}/{client_id}/{client_secret}/{targets}
Organizational Parameters
Section titled “Organizational Parameters”| Variable | Required | Description |
|---|---|---|
| source | *Yes | The email address or Object ID of the Exchange Online mailbox used to send mail. This mailbox must have the Mail.Send application permission granted. |
| tenant_id | *Yes | The Tenant ID (Directory ID) associated with your App Registration. |
| client_id | *Yes | The Client ID (Application ID) associated with your App Registration. |
| client_secret | *Yes | The Client Secret you generated in “Certificates & secrets”. |
| targets | No | One or more recipient email addresses. If omitted, the email is sent to the address identified by source. |
| to | No | Alias for targets. Useful in YAML configuration. |
| cc | No | One or more Carbon Copy addresses. Accepts comma-separated values and named addresses (e.g. Name <email@example.com>). |
| bcc | No | One or more Blind Carbon Copy addresses. Accepts comma-separated values and named addresses (e.g. Name <email@example.com>). |
| reply_to | No | One or more Reply-To addresses. When a recipient replies, their reply goes here instead of to source. Accepts comma-separated values and named addresses. |
Global Parameters
Section titled “Global Parameters”| Variable | Description |
|---|---|
| overflow | This parameter can be set to either split, truncate, or upstream. This determines how Apprise delivers the message you pass it. By default this is set to upstream 👉 upstream: Do nothing at all; pass the message exactly as you received it to the service.👉 truncate: Ensure that the message will fit within the service’s documented upstream message limit. If more information was passed then the defined limit, the overhead information is truncated.👉 split: similar to truncate except if the message doesn’t fit within the service’s documented upstream message limit, it is split into smaller chunks and they are all delivered sequentially there-after. |
| format | This parameter can be set to either text, html, or markdown. Some services support the ability to post content by several different means. The default of this varies (it can be one of the 3 mentioned at any time depending on which service you choose). You can optionally force this setting to stray from the defaults if you wish. If the service doesn’t support different types of transmission formats, then this field is ignored. |
| verify | External requests made to secure locations (such as through the use of https) will have certificates associated with them. By default, Apprise will verify that these certificates are valid; if they are not then no notification will be sent to the source. In some occasions, a user might not have a certificate authority to verify the key against or they trust the source; in this case you will want to set this flag to no. By default it is set to yes. |
| cto | This stands for Socket Connect Timeout. This is the number of seconds Requests will wait for your client to establish a connection to a remote machine (corresponding to the connect()) call on the socket. The default value is 4.0 seconds. |
| rto | This stands for Socket Read Timeout. This is the number of seconds the client will wait for the server to send a response. The default value is 4.0 seconds. |
| emojis | Enable Emoji support (such as providing :+1: would translate to 👍). By default this is set to no. Note: Depending on server side settings, the administrator has the power to disable emoji support at a global level; but default this is not the case. |
| tz | Identify the IANA Time Zone Database you wish to operate as. By default this is detected based on the configuration the server hosting Apprise is running on. You can set this to things like America/Toronto, or any other properly formated Timezone describing your area. |
Organizational Examples
Section titled “Organizational Examples”Send an email notification from an Exchange Online mailbox:
# tenant_id: ab-cd-ef-gh# source: user@example.com# client_id: zz-yy-xx-ww# secret: rt/djdwjjd (/ URL-encoded as %2F)apprise -vv -t "Test Title" -b "Test Body" \ "o365://user@example.com/ab-cd-ef-gh/zz-yy-xx-ww/rt%2Fdjdwjjd/"Send to multiple recipients with CC and BCC:
apprise -vv -t "Test Title" -b "Test Body" \ "o365://user@example.com/ab-cd-ef-gh/zz-yy-xx-ww/rt%2Fdjdwjjd/recipient@example.com?cc=manager@example.com&bcc=audit@example.com"Send with a Reply-To address (useful for contact forms - replies go to the end user, not the sending mailbox):
apprise -vv -t "Contact Form" -b "Someone contacted you" \ "o365://noreply@example.com/ab-cd-ef-gh/zz-yy-xx-ww/rt%2Fdjdwjjd/support@example.com?reply_to=customer@external.com" Questions or Feedback?
Documentation
Notice a typo or an error? Report it or contribute a fix .
Technical Issues
Having trouble with the code? Open an issue on GitHub: