ServicesAboutGet Started
ProxmoxMicrosoft GraphAmazon SES

Proxmox 9 — Email Notifications via Microsoft Graph API or Amazon SES

Route Proxmox 9 alert emails through Microsoft Graph API or Amazon SES with zero Proxmox configuration changes. No SMTP relay, no VM, no LXC — just a Postfix alias and a Python script.

How it works

A default Proxmox installation sends notification emails to the local root user via Postfix. Normally this requires an SMTP relay to reach an external mailbox — which is increasingly difficult with modern OAuth-only providers like Microsoft 365 and Google Workspace.

This guide replaces the SMTP relay with a local Postfix alias that pipes mail into a lightweight Python script. The script authenticates directly to your cloud provider's API and delivers the message — no SMTP involved.

text
Proxmox notification
  → sendmail (root)
  → Postfix local alias
  → /usr/local/sbin/proxmox-mail-api
  → Microsoft Graph API  or  Amazon SES
  → your mailbox
💡

The only Proxmox-side requirement is that the root@pam user has an email address set. Everything else happens at the Postfix and script level on the host — no new notification endpoints, no SMTP relays, no additional Proxmox UI settings.

A default Proxmox installation only has a root user, so all commands below assume you are logged in as root. This guide intentionally targets a default setup — the most common scenario we see in practice. If you know what you are doing, many of these steps can be customized to your preferences.


1. Verify the root@pam email

Proxmox will only send notifications if the root@pam user has an email address configured. Navigate to Datacenter → Permissions → Users, select root, and click Edit:

Proxmox Datacenter → Permissions → Users panel showing the root user
Datacenter → Permissions → Users — select root and click Edit

Set the E-Mail field to root@localhost. This routes the notification back into local Postfix delivery so the root alias (configured in a later step) can pipe it into the script. The actual destination mailbox is controlled by MAIL_TO in the script's config file.

Edit User dialog for root@pam showing the E-Mail field
Set the E-Mail field to root@localhost
⚠️

Do not add addresses to the Additional Recipient(s) field in the mail-to-root sendmail endpoint. Those addresses bypass the script entirely and go through Postfix SMTP — which will fail without a relay. To send notifications to multiple people, list all addresses in MAIL_TO in the config file (space-separated).


2. Obtain API credentials

Before starting, gather the credentials for your chosen provider.

Microsoft Graph API

You need an Entra ID (Azure AD) App Registration with the Mail.Send application permission granted and admin-consented. Collect:

VariableDescription
GRAPH_TENANT_IDYour Microsoft 365 tenant ID
GRAPH_CLIENT_IDThe App Registration's Application (client) ID
GRAPH_CLIENT_SECRETA client secret generated for the App Registration
GRAPH_SENDERThe mailbox the Graph API will send from (e.g. alerts@yourdomain.com)

Amazon SES

You need IAM credentials with ses:SendEmail permission. Collect:

VariableDescription
SES_REGIONAWS region for SES (e.g. us-east-1)
SES_ACCESS_KEY_IDIAM access key ID
SES_SECRET_ACCESS_KEYIAM secret access key
SES_SESSION_TOKEN(Optional) Session token if using temporary credentials
SES_SENDERVerified sender address in SES
💡

Using Amazon SES? You will also need the boto3 Python library. Install it from the Debian repository:

bash
apt install -y python3-boto3

3. Create the config file

Create /etc/default/proxmox-mail-api with your provider credentials. Choose the block that matches your provider.

Microsoft Graph API

bash
cat > /etc/default/proxmox-mail-api <<'EOF'
MAIL_PROVIDER=graph
MAIL_TO=you@yourdomain.com
CREDENTIAL_EXPIRES=2028-03-31

GRAPH_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
GRAPH_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
GRAPH_CLIENT_SECRET=your-secret-here
GRAPH_SENDER=proxmox-alerts@yourdomain.com
EOF

Amazon SES

bash
cat > /etc/default/proxmox-mail-api <<'EOF'
MAIL_PROVIDER=ses
MAIL_TO=you@yourdomain.com
CREDENTIAL_EXPIRES=2028-03-31

SES_REGION=us-east-1
SES_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
SES_SECRET_ACCESS_KEY=your-secret-key-here
SES_SENDER=proxmox-alerts@yourdomain.com
EOF

Lock down permissions

bash
chmod 644 /etc/default/proxmox-mail-api

The file must be readable by the Postfix pipe user (nobody), so 0644 is the standard permission for /etc/default/ on Debian. The script will refuse to run if the file is group- or world-writable. On a Proxmox host, only root has shell access by default.

💡

MAIL_TO is where your notifications actually end up — this can be a single address or multiple addresses separated by spaces. The root@localhost email on root@pam is just a routing trick to keep mail local; it is not the destination.

💡

/etc/default/ is the standard Debian location for service configuration files — equivalent to /etc/sysconfig/ on RHEL/Fedora.


4. Install the mail script

Download the proxmox-mail-api Python script to /usr/local/sbin/:

bash
wget -O /usr/local/sbin/proxmox-mail-api https://meikakuconsulting.com/guides/proxmox/proxmox-mail-api
chmod 755 /usr/local/sbin/proxmox-mail-api

Verify the script compiles

bash
python3 -m py_compile /usr/local/sbin/proxmox-mail-api

If this command produces no output, the script is syntactically valid.

💡

The Graph provider uses only Python 3 standard library modules — no additional packages required. Proxmox ships with Python 3 out of the box. The SES provider additionally requires python3-boto3 from the Debian repository.


5. Set the Postfix alias

Add a root alias that pipes all local mail into the script. This command is idempotent — it replaces an existing root: line if present, or appends one if not:

bash
grep -q '^root:' /etc/aliases \
  && sed -i 's|^root:.*|root: "|/usr/local/sbin/proxmox-mail-api"|' /etc/aliases \
  || echo 'root: "|/usr/local/sbin/proxmox-mail-api"' >> /etc/aliases

newaliases
💡

This intercepts all local mail to root on this host — including cron job output and other system notifications. On a Proxmox host, this is usually exactly what you want.


6. Remove the SMTP relay (if configured)

If Postfix is currently configured with a relay host, clear it so mail stays local:

bash
postconf -e relayhost=
systemctl restart postfix

You can verify it is cleared:

bash
postconf relayhost

The output should show relayhost = with no value.


7. Test

The easiest way to test is directly from the Proxmox web UI. Navigate to Datacenter → Notifications, select the mail-to-root target, and click Test:

Proxmox Datacenter Notifications panel — selecting mail-to-root and clicking Test
Datacenter → Notifications — select mail-to-root, click Test, then click Yes

Click Yes to send the test notification. You should see a confirmation:

Proxmox confirmation dialog showing 'Sent test notification to mail-to-root'
Success — the test notification was sent through the pipeline

If everything is configured correctly, the email should arrive at the address you set in MAIL_TO.

Verify from the command line

You can also test the full pipeline from the shell:

bash
echo "Test from Proxmox mail API" | sendmail root

Check the mail log for errors:

bash
journalctl -u postfix -n 50 --no-pager

Isolate script issues

If the alias is not triggering, test the script directly to isolate the issue:

bash
cat <<'EOF' | /usr/local/sbin/proxmox-mail-api
From: proxmox@localhost
To: root
Subject: Direct API test

This is a direct test bypassing Postfix.
EOF

If the direct test works but the alias does not, the problem is in Postfix alias configuration. If the direct test fails, the problem is in your API credentials.


8. Recommended hardening

Credential expiration tracking

Set CREDENTIAL_EXPIRES in the config file to the expiration date of your client secret or IAM access key (format: YYYY-MM-DD). The script automatically checks this on every run and prepends a warning banner to the email body when credentials are within 60 days of expiring or already expired.

Restrict the Graph sender mailbox

In Microsoft 365, use application access policies in Exchange Online to restrict the app registration so it can only send as the intended alert mailbox.


Troubleshooting

SymptomLikely Cause
Config file not found/etc/default/proxmox-mail-api does not exist. Create it per step 2.
permissions are 0o666, which allows group or world writesRun chmod 644 /etc/default/proxmox-mail-api.
MAIL_PROVIDER must be 'graph' or 'ses'The MAIL_PROVIDER line is missing or has a typo in the config file.
Failed to obtain Graph tokenCheck GRAPH_TENANT_ID, GRAPH_CLIENT_ID, and GRAPH_CLIENT_SECRET. Verify the app has Mail.Send permission with admin consent.
Graph sendMail failedThe GRAPH_SENDER mailbox may not exist or the app is not authorized to send as that mailbox.
boto3 is requiredRun apt install -y python3-boto3.
SES send_email failedVerify IAM credentials, region, and that the sender address is verified in SES.
Script works directly but not through PostfixVerify the alias with postconf alias_maps. Run newaliases and restart Postfix. Check that the script is executable (chmod 755).
No email arrives but no errors in logsConfirm MAIL_TO is correct. Check spam folders. For Graph, verify the message appears in the sender's Sent Items.