Use the S/MIME API to upload certificates and assign them to email accounts in bulk, for example when you roll out S/MIME to many users or renew certificates every year. Everything here can also be done by hand in Settings › S/MIME Certificates.
Before you begin
- Create an API key and note your organization ID. See mxHERO V3 API Key.
- Have the certificates ready:
- P12 (PKCS #12) files, with their passwords, for your own users. These contain the private key.
- PEM files for external partners. These contain only the public certificate.
- An S/MIME rule uses the certificates. Uploading certificates alone doesn't sign or encrypt anything.
Concepts
Stored certificate. A certificate you uploaded. mxHERO gives it a certificateId (issuer DN plus serial number).
| Type | Contains | Sign | Decrypt | Encrypt to | Verify |
|---|---|---|---|---|---|
| P12 (internal users) | Public and private key | Yes | Yes | Yes | Yes |
| PEM (external partners) | Public key only | No | No | Yes | Yes |
When you upload a P12, mxHERO checks the password, then re-encrypts the file with an organization-specific password. Your original password is not stored.
Email account. An address that certificates are assigned to:
| Field | Meaning |
|---|---|
email |
The address (primary key). |
primaryCertificateId |
The certificate used now: to sign outgoing mail (P12 only) and as the target when others encrypt to this address. Only one at a time. |
certificateHistory |
Every certificate this address has used, including the primary. Used to decrypt and verify older mail. |
Rules:
- The primary certificate must also be in
certificateHistory. Setting a primary adds it to the history. - Only addresses in your organization's domains can sign. External addresses are used for encryption and verification only.
- A certificate assigned to an email account can't be deleted. Remove it from the accounts first.
Endpoints
All paths are relative to https://api.mxhero.com/v3/organizations/{organizationId}/smime. Send x-api-key: {api-key} and, for bodies, Content-Type: application/json.
Certificates
| Method | Path | Purpose |
|---|---|---|
POST |
/certificates/upload/p12 |
Upload a P12. Body: certificateData (Base64), password, friendlyName, optional description. |
POST |
/certificates/upload/pem |
Upload a PEM. Body: certificateData (PEM text), friendlyName, optional description. |
GET |
/certificates?limit=20&offset=0 |
List certificates. Optional filter query (free text: subject, issuer, email). |
GET |
/certificates/{certificateId} |
Get one certificate. |
PATCH |
/certificates/{certificateId} |
Change friendlyName and/or description. |
Email accounts
| Method | Path | Purpose |
|---|---|---|
POST |
/email-accounts |
Create an account. Body: email, primaryCertificateId, certificateHistory. |
GET |
/email-accounts?limit=20&offset=0 |
List accounts. |
GET |
/email-accounts/{email} |
Get one account. |
POST |
/email-accounts/{email}/certificates/{certificateId}/set-primary |
Make a certificate the primary. |
POST |
/email-accounts/{email}/certificates/{certificateId}/add-to-history |
Add a certificate to the history only. |
URL-encode {email} and {certificateId} in paths.
Set up signing for an internal user
-
Base64-encode the user's P12 file.
base64 -w0 jane.doe.p12 > jane.doe.p12.b64On macOS use
base64 -i jane.doe.p12 -o jane.doe.p12.b64. -
Upload it.
curl -X POST "https://api.mxhero.com/v3/organizations/{organizationId}/smime/certificates/upload/p12" \ -H "x-api-key: {api-key}" \ -H "Content-Type: application/json" \ -d '{ "certificateData": "MIIJKQIBAzCCCO8G...", "friendlyName": "Jane Doe signing certificate", "password": "{p12-password}" }'The response is the stored certificate. Copy its
certificateId. -
Create the email account with that certificate as primary.
curl -X POST "https://api.mxhero.com/v3/organizations/{organizationId}/smime/email-accounts" \ -H "x-api-key: {api-key}" \ -H "Content-Type: application/json" \ -d '{ "email": "jane.doe@example.com", "primaryCertificateId": "{certificateId}", "certificateHistory": ["{certificateId}"] }'
Set up encryption to an external partner
-
Upload the partner's PEM certificate.
curl -X POST "https://api.mxhero.com/v3/organizations/{organizationId}/smime/certificates/upload/pem" \ -H "x-api-key: {api-key}" \ -H "Content-Type: application/json" \ -d '{ "certificateData": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----", "friendlyName": "Partner certificate - partner.example.net" }'In JSON, write each line break of the PEM file as
\n. Create an email account for the partner's address with that certificate as primary (same call as step 3 above, with the partner's address).
Renew a certificate
Keep the old certificate in the history so older mail can still be decrypted and verified.
Upload the new P12 and copy the new
certificateId.-
Add it to the account's history.
curl -X POST "https://api.mxhero.com/v3/organizations/{organizationId}/smime/email-accounts/jane.doe%40example.com/certificates/{newCertificateId}/add-to-history" \ -H "x-api-key: {api-key}" -
Make it the primary.
curl -X POST "https://api.mxhero.com/v3/organizations/{organizationId}/smime/email-accounts/jane.doe%40example.com/certificates/{newCertificateId}/set-primary" \ -H "x-api-key: {api-key}"
Other operations
# List certificates (page of 20)
curl -X GET "https://api.mxhero.com/v3/organizations/{organizationId}/smime/certificates?limit=20&offset=0" \
-H "x-api-key: {api-key}"
# Rename a certificate
curl -X PATCH "https://api.mxhero.com/v3/organizations/{organizationId}/smime/certificates/{certificateId}" \
-H "x-api-key: {api-key}" \
-H "Content-Type: application/json" \
-d '{"friendlyName": "Jane Doe 2027", "description": "Renewed October 2026"}'
# Get one email account
curl -X GET "https://api.mxhero.com/v3/organizations/{organizationId}/smime/email-accounts/jane.doe%40example.com" \
-H "x-api-key: {api-key}"Responses and errors
| Status | Meaning |
|---|---|
200 / 201
|
Success. |
400 |
Validation failed, for example a missing P12 password ("Password is required for P12 certificates"), a wrong password, an invalid certificate, or a primary certificate that isn't in the history. |
404 |
The certificate or email account doesn't exist. |
409 |
Conflict, for example the email account already exists, or the certificate is in use and can't be deleted. |
500 |
Server error. Retry later; if it persists, contact support. |
Error bodies look like this:
{
"error": "Validation failed",
"message": "primaryCertificateId must be present in certificateHistory",
"timestamp": "2026-01-15T10:30:00Z"
}Best practices
- Keep P12 passwords in a secrets manager; you need them only for the upload.
- Use strong P12 passwords.
- Track expiry dates and renew before certificates expire.
- Never remove old certificates from the history while mail encrypted with them may still arrive or need verifying.
Verify
-
GET /email-accounts/{email}returns the account with the expectedprimaryCertificateId. - In the dashboard, open Settings › S/MIME Certificates and find the certificate.
- Send a test message through an S/MIME rule and check the result in Email Flow Logs.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
400 on P12 upload |
Missing or wrong password, or the data isn't valid Base64 | Re-encode the file without line breaks; check the password. |
| Mail isn't signed | The sender's primary certificate is a PEM (no private key), the address isn't in your domains, or no S/MIME rule covers the sender | Upload a P12 and set it as primary; check the rule. |
| Recipients can't decrypt old mail after renewal | The old certificate was removed from history | Add the old certificate back with add-to-history. |
409 creating an account |
The account already exists | Use set-primary or add-to-history on the existing account. |
Comments
0 comments
Please sign in to leave a comment.