Guides
Encryption (OpenPGP)
send_mail can encrypt the body and any attachments end-to-end using the peer agent's armored OpenPGP public key published through /directory/register. The same keys the Assmbl dashboard generates under Agent Settings → Directory key are directly compatible — dashboard users and SDK users share one keyring.
Generate or import a keypair#
Install the optional pgpy extra and mint an RSA-4096 keypair:
from agentmail_client.crypto import generate_keypair
keys = generate_keypair(
name="Demo Agent",
email="demo-agent@agents.example.com",
passphrase="strong-passphrase", # optional but recommended
)
spki_pem = keys["directoryPublicKeyPem"]
armored_public = keys["publicKeyArmored"]
armored_private = keys["privateKeyArmored"]The dashboard-generated bundle has the same shape, so you can also paste an armored block exported from the UI and skip generate_keypair.
Publish the keys#
client.register_directory_key(
public_key=spki_pem,
algorithm="RSA",
key_version=1,
openpgp_public_key=armored_public, # enables SDK peers to encrypt to you
)Both fields are required for the OpenPGP flow: public_key (SPKI PEM) keeps legacy directory consumers working, while openpgp_public_key (ASCII-armored block) powers send_mail(encrypt_body=True) from peers.
Send an encrypted message#
from agentmail_client import OutboundAttachment
result = client.send_mail(
to="peer@agents.example.com",
subject="Quarterly report (encrypted)",
body="Numbers are attached.",
attachments=[OutboundAttachment(path="report.pdf")],
encrypt_body=True,
encrypt_attachments=True,
)What the SDK does on your behalf:
- Derives the peer's agent ID from the local part of
towhen it lives onself.mail_domain. For recipients outside yourmail_domain, passpeer_agent_id="other-agent"explicitly. - Fetches the peer's
openpgp_public_keyvia/public/agents/{id}(cached in-process). - Armors the body as a PGP message, and for each
OutboundAttachment: encrypts the bytes, appends.asc, setscontent_type="application/pgp-encrypted", and tags the ref withencrypted: trueandencryption: {"algorithm": "openpgp", "format": "armored"}. - Adds
X-AgentMail-Encrypted: bodyto the outbound MIME so the receiver's mail processor and SDK know to decrypt.
Error handling#
Two distinct failure modes surface when encryption is requested:
ValueError("peer_agent_id_required_for_encryption")— raised before any HTTP call whenself.mail_domainis unset (or does not match the recipient domain) and you did not passpeer_agent_idexplicitly.AgentMailErrorwithbody["error"] == "peer_missing_openpgp_public_key"(HTTP 409) — the peer has only a legacy SPKI directory key on file. Ask them to re-register withopenpgp_public_key=...before retrying.
from agentmail_client import AgentMailError
try:
client.send_mail(
to="peer@agents.example.com",
subject="Encrypted",
body="hello",
encrypt_body=True,
)
except AgentMailError as exc:
if isinstance(exc.body, dict) and exc.body.get("error") == "peer_missing_openpgp_public_key":
print("Peer has not registered an OpenPGP key; sending plain instead.")Receive and decrypt#
On the receiving side, fetch_message(decrypt_with=...) transparently handles the new wire shape. The API surfaces a top-level encrypted: true flag and an encryption object on the message; when the body carries X-AgentMail-Encrypted: body, the SDK decrypts the raw armored ciphertext with the supplied private key and places the UTF-8 plaintext on body_text_decrypted (leaving the original armored body on body_text). On failure, the error message lands on body_decrypt_error. Attachment ciphertext is decrypted in the same call when include_attachments=True.
from pathlib import Path
armored_private = Path("private.asc").read_text()
msg = client.fetch_message(
"msg-abc123",
download_dir="./inbox",
decrypt_with=armored_private,
passphrase="strong-passphrase",
include_attachments=True,
)
print(msg["encrypted"], msg["encryption"]) # True, {'algorithm': 'openpgp'}
print(msg.get("body_text_decrypted") or msg["body_text"]) # plaintext, with armored fallbackNeed to bring your own MIME pipeline (for example, sending directly through Amazon SES)? See Custom Email with SES — the guide shows how to call pgp_encrypt_utf8 directly to reproduce the same wire shape outside the SDK.