> ## Content Index
> Fetch the complete content index at: https://www.vikepmlab.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Automating Oracle EPM Level0 Backups — With Email Reports, No Third-Party API Needed
- URL: https://www.vikepmlab.com/automating-oracle-epm-level0-backups-with-email-reports-no-third-party-api-needed/
- Published: 2026-08-19T15:24:48.000Z
- Updated: 2026-08-19T15:24:48.000Z
- Author: Vikram Kumar

If you run Oracle EPM Cloud (Planning, FreeForm, FCCS, or any of its siblings), you've probably reached for `epmautomate exportEssbaseData` at some point to pull a Level0 backup of a cube. It works well — but running it by hand, or firing it from cron with zero visibility into whether it actually succeeded, gets old fast.

This post walks through a small setup I built to fix that: a real EPM Automate backup script paired with a generic, dependency-light Python notifier that emails an HTML status report after every run — no third-party email service, no API key, nothing beyond Python's own standard library and a Bash script you can read top to bottom in five minutes.

The full code is on GitHub: [**vikepmlab/epm-level0-backup**](https://github.com/VIKRAM-EPM/vikepmlab/tree/main/epm-level0-backup?ref=vikepmlab.com)

---

## The problem with a bare backup script

A typical Level0 export looks like this:

```bash
epmautomate exportEssbaseData CUBE_NAME FILE_NAME level=0

```

That one line does real work — but on its own it tells you nothing after the fact. Did it succeed? Did the download from the outbox actually land? Is the outbox quietly filling up with old exports nobody's cleaning up? A cron job that only writes to a log file nobody reads isn't much better than not having a backup job at all.

So the setup here does three things:

1. **Runs the actual Level0 export and download**, for any number of cubes — BSO, Hybrid, or ASO. (Turns out `level=0` is valid for all three per Oracle's own docs — only `level=All` is BSO/Hybrid-only, so one script covers every cube type without branching logic.)
2. **Writes a structured JSON status file** after every run — machine readable, not just a log dump.
3. **Emails an HTML report** summarizing what happened, using nothing but Python's built-in `smtplib` — works with Gmail, Office365, or an internal relay server your org already runs.

## Why the script clears the EPM outbox before every export

This part's easy to miss if you've never had it bite you: the zip `exportEssbaseData` produces lands in the EPM Cloud outbox and stays there — it isn't cleaned up automatically. Left alone, that outbox accumulates one export per cube per run, forever. That's not just untidy; a growing outbox eats into your application's storage allocation and slows down anything that lists it, including the verification step this script itself relies on.

So before every export, the script deletes any zip left over from a previous run for that cube, verifies it's actually gone, and only then kicks off the new export. Small step, but it keeps the outbox at a flat, predictable footprint run over run instead of growing without bound.

## Plain SMTP, no account signup required

It's tempting to reach for a transactional email API for something like this — but that means an account, an API key to protect, and one more service to depend on for a job that just needs to send one email after it finishes. Python's `smtplib` and `email` modules do the same job with zero third-party dependencies:

```python
with smtplib.SMTP(smtp_host, smtp_port) as server:
    server.starttls()
    server.login(username, password)
    server.sendmail(from_address, recipients, message.as_string())

```

That works against Gmail (with an App Password), Office365, or — the pattern most organizations actually use — an internal relay server that's allow-listed by hostname and doesn't need a login at all. The config file has a toggle for exactly that (`auth_required = true` or `false`), so the same script covers both a personal test setup and a real internal deployment.

## Everything lives in one config file

No environment variables to hunt down, no code to edit. A single `config.ini` (using Python's built-in `configparser` — no extra package needed) holds the job name, the cube list, retention policy, and email/SMTP settings:

```ini
[smtp]
host = smtp.office365.com
port = 587
use_tls = true
auth_required = true
username = you@example.com
password_env_var = BACKUP_NOTIFIER_SMTP_PASSWORD

```

The password itself is never written to disk — it's pulled from an environment variable at runtime, or (for quick local testing only) directly from the file with a clear warning not to commit it that way.

---

## Try it yourself

Rather than just describe the code, here's the actual logic — click through and see the real export command, the real outbox-cleanup block, and a live-rendered version of the email report. The last tab runs genuine Python in your browser via [Pyodide](https://pyodide.org/?ref=vikepmlab.com) — edit the sample data and watch the report regenerate in real time, no server involved.

epm-level0-backup — interactive walkthrough 

$ 

Ask the script

$show export logic $show outbox cleanup $preview email $run epmautomate console $try it — build a report 

### The Level0 export logic real code

From `epm_level0_backup.sh`. Works the same way for BSO, Hybrid, and ASO cubes — Oracle's docs confirm `level=0` is valid for all three; only `level=All` is BSO/Hybrid-only.

# -------- STEP 1: Run Level0 Export --------
log "[STEP 1] Running Level0 export for cube: $CUBE"
run_to_log "$EPM_BIN/epmautomate.sh" exportEssbaseData \
    "$CUBE" \
    "$EPM_ZIP_NAME" \
    level=0
EXPORT_RC=$?

📄 epm\_level0\_backup.sh · lines 172–180 

# -------- STEP 2: Download zip from EPM outbox --------
log "[STEP 2] Downloading $EPM_ZIP_NAME..."
run_to_log "$EPM_BIN/epmautomate.sh" downloadFile \
    "$EPM_ZIP_OUTBOX_PATH" \
    "$LOCAL_ZIP_PATH"
DOWNLOAD_RC=$?

📄 epm\_level0\_backup.sh · lines 195–200 

### Why the outbox gets cleared first real code

The exported zip lands in the EPM Cloud outbox and stays there until removed. A growing pile of old export files eats into storage and slows down anything that lists the outbox — so this runs before every export, not after.

# -------- PRE-STEP: Clear any stale zip left in the outbox --------
run_to_log "$EPM_BIN/epmautomate.sh" deletefile "$EPM_ZIP_OUTBOX_PATH"
PRE_DELETE_RC=$?

if [[ $PRE_DELETE_RC -ne 0 ]]; then
  log "[INFO] No existing file found — proceeding to export."
else
  log "[INFO] Existing outbox file deleted — verifying..."
  # confirm it's really gone before trusting a clean export
fi

📄 epm\_level0\_backup.sh · lines 148–170 (23 lines total — condensed above; full verification/error-handling branch lives in the file) 

### What the report email looks like real output

This is the actual output of `build_html()` in `backup_notifier.py`, rendered once on sample data — not a mockup.

### EPM Automate console simulated

Click a command to see the output a real run produces. This doesn't connect to any live EPM instance or send any credentials anywhere — there's nothing here for it to connect to.

login deletefile exportEssbaseData downloadFile logout 

$ 

### Try it — build your own report runs live in your browser

Edit the sample data below and run it. This is real Python — [Pyodide](https://pyodide.org/?ref=vikepmlab.com) runs it via WebAssembly right here in your browser. Nothing is sent to a server.

{ "overall\_status": "SUCCESS", "run\_date": "2026-08-16 02:00:00", "hostname": "your-server", "backup\_dir": "/archive/backup", "retention\_days": 90, "purge\_count": 0, "cubes": \[ {"cube": "DemoCube", "status": "SUCCESS", "started": "02:00:03", "completed": "02:04:41", "elapsed": "4.63mins", "local\_file": "DemoCube\_level0.zip", "file\_size": "184MB", "notes": "Backup downloaded successfully"} \] } Run ▸ loading Python runtime… 

---

## Get the code

The full project — `epm_level0_backup.sh`, `backup_notifier.py`, and `config.ini` — is up on GitHub: [**vikepmlab/epm-level0-backup**](https://github.com/VIKRAM-EPM/vikepmlab/tree/main/epm-level0-backup?ref=vikepmlab.com)

Setup instructions, troubleshooting, and the full JSON status contract are all in the repo's README. If you're running this in an organization with an internal SMTP relay rather than a personal email account, that's covered too — no code changes needed, just a couple of config lines.

*Provided as-is — review and test in a non-production environment before relying on it in yours.*