---
title: Integrating OpenBao with KumoMTA
description: "Guest Post:  How CM.com integrated OpenBao with KumoMTA for Keysource storage"
image: https://kumomta.com/hubfs/baobun-vault_1.91x1_1200x628.png
---

[Skip to content](https://kumomta.com/blog/integrating-openbao#main-content)

[![KUMO\_Logo\_NEW\_Color](https://kumomta.com/hs-fs/hubfs/KUMO_Logo_NEW_Color.png?width=118&height=32&name=KUMO_Logo_NEW_Color.png "KUMO_Logo_NEW_Color")](https://kumomta.com)

- Technology 
    - [Architecture](https://kumomta.com/technology/architecture)
    - [Features](https://kumomta.com/technology)
    - [Open Source](https://kumomta.com/technology/open-source)
- [Support & Pricing](https://kumomta.com/pricing)
- About Kumo 
    - [About Us](https://kumomta.com/about)
    - [Events](https://kumomta.com/events)
    - [Customers](https://kumomta.com/customers)
    - [Contact Us](https://kumomta.com/contact)
- Resources 
    - [Documentation](https://docs.kumomta.com/)
    - [Migration Center](https://kumomta.com/migration-center) 
          - [Migration Center Hub](https://kumomta.com/migration-center)
          - [Momentum](https://kumomta.com/momentum-migration-guide)
          - [PowerMTA](https://kumomta.com/replace-powermta-with-kumomtas-infrastructure-built-for-mailops)
          - [Halon](https://kumomta.com/halon-migration-guide)
          - [SendGrid](https://kumomta.com/sendgrid-alternative-migration)
          - [Hurricane MTA](https://kumomta.com/hurricanemta-alternative-socketlabs-migration)
    - [Blog](https://kumomta.com/blog)
    - [Community Forum](https://community.kumomta.com/)
    - Guides & Reports 
          - [2026 MTA Buyers Guide](https://kumomta.com/buyers-guide-2026-download)
          - [2026 State of MailOps](https://kumomta.com/2026-state-of-mailops-report-download-page)
    - [YouTube Channel](https://www.youtube.com/channel/UC348UpfbnPgpKPAXIBW_JNQ)
    - [Github Repository](https://github.com/KumoCorp/kumomta)

- [Contact](https://kumomta.com/contact)

- [DKIM](https://kumomta.com/blog/tag/dkim),
- [SMTP](https://kumomta.com/blog/tag/smtp),
- [KumoMTA Integrations](https://kumomta.com/blog/tag/kumomta-integrations),
- [OpenBao](https://kumomta.com/blog/tag/openbao)

# Integrating OpenBao with KumoMTA

![](https://kumomta.com/hubfs/baobun-vault_1.91x1_1200x628.png)

- September 10, 2026

![Tom Mairs](https://app.hubspot.com/settings/avatar/50c5bbe56f0a485919666fb3f0a598f7)

[Tom Mairs](https://kumomta.com/blog/author/tom-mairs)

This is effectively a guest post from [CM.com](https://www.cm.com/), which uses [KumoMTA](https://github.com/KumoCorp/kumomta) as its email infrastructure. Their Engineering team shared this with us as an example of how they integrated [OpenBao](https://openbao.org/) as a KeySource backend for KumoMTA and we are excited to share it with the community. The full submission is reproduced below, mostly unchanged (edited for formatting only). Enjoy.

 

## Using OpenBao as a KeySource backend for KumoMTA

**Credit:** this write-up is based on integration and migration work done at [CM.com](https://www.cm.com/) by Adithya V Hebbar, Software Engineer, CM.com, with the OpenBao cluster itself set up and operated by Cyril Mengin and the CM.com Ops team.

[OpenBao](https://openbao.org/) is a Linux Foundation-governed, open-source fork of HashiCorp Vault, created after Vault's 2023 license change to BSL. Because OpenBao implements the same KV v2 HTTP API as Vault, it works as a drop-in replacement for every `vault_*` KeySource field documented in the [KumoMTA KeySource reference](https://docs.kumomta.com/reference/keysource/#hashicorp-vault). No KumoMTA-side code changes are required.

We verified this in a production environment for two separate KumoMTA integration points:

- **DKIM signing keys**, via `kumo.dkim.rsa_sha256_signer`'s `key` field
- **SMTP AUTH credentials**, via a direct `kumo.secrets.load` call inside a `smtp_server_auth_plain` handler

### How we set it up

1. We stood up an OpenBao server or cluster. In this case, the deployment was OpenBao v2.4.4 via the official [Helm chart](https://openbao.org/docs/platform/k8s/helm/), chart version 0.19.3. The cluster is shared and operated by the platform and ops team, with each internal team assigned its own namespace.
2. We enabled a KV v2 secrets engine at a mount path:
   
   ```
   $ bao secrets enable -path=secret kv-v2
   ```
3. We wrote a policy that scopes read access to only the paths KumoMTA needs:
   
   ```
   path "secret/data/dkim/*" {
     capabilities = ["read"]
   }
   
   path "secret/data/smtp-auth/*" {
     capabilities = ["read"]
   }
   
   # lets the token renew/look itself up instead of going stale
   path "auth/token/*" {
     capabilities = ["create", "read", "update", "list"]
   }
   ```
4. We created an AppRole tied to that policy, then generated a token from it. The `role_id` and `secret_id` act roughly like a one-time username and password pair used to mint the token. We then made the resulting token available to the `kumod` process through `VAULT_ADDR` and `VAULT_TOKEN`.
5. We pointed KumoMTA's existing Vault-shaped configuration fields at the OpenBao endpoint exactly as if it were HashiCorp Vault. That was the full change.

### Configuration examples

*Secrets redacted.*

#### DKIM signer

```
local vault_signer = kumo.dkim.rsa_sha256_signer {
  key = {
    vault_mount = 'secret',
    vault_path = 'dkim/' .. msg:from_header().domain,

    -- Same as HashiCorp Vault: if omitted, values are read from
    -- $VAULT_ADDR / $VAULT_TOKEN. These must be accessible to the
    -- kumod user/service.

    -- vault_address = "https://vault.internal.example.com"
    -- vault_token = "hvs.TOKENTOKENTOKEN" -- OpenBao issues the same token format
    -- vault_key = "my_custom_key_name"
  },
}
```

Populate it with the OpenBao CLI, which mirrors Vault syntax:

```
$ bao kv put -mount=secret dkim/example.org key=@example-private-dkim-key.pem
```

We have not specifically verified the original HashiCorp `vault` binary against an OpenBao endpoint. We only used `bao`, which is the supported and recommended CLI for OpenBao. In principle, the original Vault CLI should also work because the wire API is compatible, but treat that as unconfirmed rather than tested.

#### SMTP AUTH credential check

This example uses `kumo.secrets.load` directly inside a `smtp_server_auth_plain` handler:

```
kumo.on('smtp_server_auth_plain', function(authz, authc, password, conn_meta)
  local ok, secret = pcall(kumo.secrets.load, {
    vault_address = os.getenv 'VAULT_ADDR',
    vault_token = os.getenv 'VAULT_TOKEN',
    vault_mount = 'secret',
    vault_path = 'smtp-auth/' .. authc,
    vault_key = 'password', -- field name is up to you; defaults to "key"
  })
  if not ok then
    return false
  end
  return secret == password
end)
```

#### Token refresher

This pattern keeps the AppRole-issued token alive using KumoMTA's own task and event primitives, with no external sidecar or cron required:

```
-- token_refresher.lua
local kumo = require 'kumo'
local http_client = kumo.http.build_client({})

local mod = {}

local function renew_token(vault_addr)
  local token = os.getenv 'VAULT_TOKEN'
  local base = vault_addr:sub(-1) == '/' and vault_addr or (vault_addr .. '/')
  local req = http_client:post(base .. 'v1/auth/token/renew-self')
  req:headers { ['X-Vault-Token'] = token }
  local resp = req:send()
  if not resp:status_is_success() then
    error(string.format('token renew failed: %s', resp:text()))
  end
end

-- registers the periodic renewal loop as a named event
function mod.register(delay)
  local vault_addr = os.getenv 'VAULT_ADDR'
  local delay_time = (not delay or delay <= 0) and 14400 or delay -- default 4h
  kumo.on('token_refresher', function()
    while true do
      local ok, err = pcall(renew_token, vault_addr)
      if not ok then
        kumo.log_error('token_refresher: renewal failed: ', tostring(err))
      else
        kumo.log_info 'token refreshed successfully'
      end
      kumo.time.sleep(delay_time)
    end
  end)
end

-- kicks off the loop as a background task
function mod.start()
  kumo.spawn_task { event_name = 'token_refresher', args = {} }
end

return mod
```

#### Wiring it up in `init.lua`

```
local token_refresher = require 'token_refresher'

-- registers the handler at module load time
token_refresher.register()

kumo.on('init', function()
  -- ...rest of init...

  -- starts the background loop once kumod is initialized
  token_refresher.start()
end)
```

This hits OpenBao's `auth/token/renew-self` endpoint directly, the same endpoint exposed by Vault, on a fixed interval using the token's own renewal capability. That is why the AppRole policy needs `auth/token/*` read and update access. There is no dependency on an OpenBao or Vault Agent sidecar.

### Gotchas and tips

- **KV v2 `data/` segment:** KumoMTA's Vault client adds the `data/` segment internally for the KV v2 API. Your `vault_mount` and `vault_path` values should match what you would pass to `vault kv put` or `bao kv put`, with no `data/` segment included, even though your ACL policy paths do need it. For example: `secret/data/dkim/*`.
- **Namespaces:** If you use OpenBao's namespace feature, note that KumoMTA KeySource has no dedicated `vault_namespace` field and only supports the standard HashiCorp Vault-shaped fields. There is no separate namespace header option. A practical workaround is to prefix the namespace directly into the mount path:

```
vault_mount = 'my-namespace/secret', -- "<namespace>/<mount>"
```

That was sufficient in practice on a shared OpenBao cluster where each internal team had its own namespace.

- **AppRole over static tokens:** Rather than minting one long-lived broad token, use a scoped flow of policy to AppRole to `role_id`/`secret_id` to token. That keeps the token available to `kumod` limited to the read capabilities it actually needs, plus `auth/token/*` so it can renew itself through the refresher process. Scope read policies narrowly by path prefix, such as separate rules for `dkim/*` and `smtp-auth/*`.
- **Migrating existing secrets from Vault:** If you are moving an existing Vault-backed KumoMTA setup to OpenBao rather than starting fresh, the data copy is straightforward because both systems speak the same KV v2 API. Options considered here included [medusa](https://github.com/jonasvinther/medusa), a purpose-built Vault KV import/export CLI that can pipe `medusa export | medusa import` directly from Vault to OpenBao; a short script driving the `vault` and `bao` CLIs directly; or a small custom program using an HTTP client library such as VaultSharp for tighter batching and retry control. For a small number of secrets, any of the three approaches works well, and the CLI-script route requires the least new tooling.

### Credits: CM.com; Adithya V Hebbar, Software Engineer, CM.com; Cyril Mengin and the CM.com Ops team for OpenBao cluster setup and operation.

 

We love user stories. If your team has done something awesome with KumoMTA, please let us know.

\- - - - - - - - -

KumoMTA is the first open-source MTA designed from the ground up for the world's largest commercial senders. We are fueled by Professional Services and Sponsorship revenue.

[Join the Forum](https://forum.kumomta.com/) | [Review the Docs](https://docs.kumomta.com/) | [Read the Blog](https://kumomta.com/blog) | [Grab the Code](https://github.com/KumoCorp/kumomta) | [SWAG Shop](https://swag.kumomta.com/)

STAY IN TOUCH

## Get periodic updates about the latest releases, features, and news around KumoMTA.

[![KUMO\_Logo\_Files\_Color 1](https://kumomta.com/hubfs/KUMO_Logo_Files_Color%201.svg "KUMO_Logo_Files_Color 1")](https://kumomta.com)

##### Proud member of

![maawg 1](https://kumomta.com/hubfs/maawg%201.png "maawg 1")

[Follow us on LinkedIn](https://www.linkedin.com/company/91414337/) [Follow us on Github](https://github.com/KumoCorp/kumomta) [Follow us on Discord](https://discord.com/servers/kumomta-1072980126737907824) [Follow us on Youtube](https://www.youtube.com/channel/UC348UpfbnPgpKPAXIBW_JNQ)

##### Explore KumoMTA

- [Open Source](https://kumomta.com/technology/open-source)
- [Features](https://kumomta.com/technology)
- [Pricing](https://kumomta.com/pricing)
- [Case Studies](https://kumomta.com/customers)
- [About Us](https://kumomta.com/about)
- [Privacy](https://kumomta.com/privacy)

##### Resources

- [Blog](https://kumomta.com/blog)
- [Documentation](https://docs.kumomta.com/)
- [Community Forum](https://community.kumomta.com/?_gl=1*o6wdcs*_gcl_au*MTAxMDA2MjM0NC4xNzcyNzI4Njg3*_ga*MTQ0NjAzODcyNS4xNzU3MDkzMjQ2*_ga_LNBCW1KF8J*czE3Nzc5MzU3ODYkbzIkZzAkdDE3Nzc5MzYwMjMkajI3JGwwJGgw)
- [FAQ](https://docs.kumomta.com/faq/)
- [YouTube Channel](https://www.youtube.com/channel/UC348UpfbnPgpKPAXIBW_JNQ)
- [GitHub Repository](https://github.com/KumoCorp/kumomta?_gl=1*12d5gs8*_gcl_au*MTAxMDA2MjM0NC4xNzcyNzI4Njg3*_ga*MTQ0NjAzODcyNS4xNzU3MDkzMjQ2*_ga_LNBCW1KF8J*czE3Nzc5MzU3ODYkbzIkZzAkdDE3Nzc5MzU3ODkkajU3JGwwJGgw)

- [© 2026 Kumo Corp, All rights reserved. KumoMTA and the Kumo logo are registered trademarks of Kumo Corp. ](https://kumomta.com/privacy)

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Tom Mairs",
    "url" : "https://kumomta.com/blog/author/tom-mairs"
  },
  "dateModified" : "2026-09-22T18:20:37.507Z",
  "datePublished" : "2026-09-10T15:41:47.000Z",
  "headline" : "Integrating OpenBao with KumoMTA",
  "image" : [ "https://kumomta.com/hubfs/baobun-vault_1.91x1_1200x628.png" ],
  "mainEntityOfPage" : {
    "@id" : "https://kumomta.com/blog/integrating-openbao",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://kumomta.com/hubfs/KUMO_Logo_NEW_Color.png"
    },
    "name" : "KumoMTA"
  }
}
```