Secure Messages
Send encrypted emails with end-to-end AES-256-GCM encryption, passphrase-based decryption, expiry, and sender revocation.
Overview
Secure messages provide end-to-end encrypted email delivery. The email content is encrypted with AES-256-GCM using a passphrase-derived key before being stored. Recipients must enter the correct passphrase to decrypt and read the message in their browser. The PushMail server never has access to the plaintext content or the encryption key.
Key features:
- AES-256-GCM encryption with PBKDF2 key derivation (200,000 iterations)
- Split-key architecture: separate verification and encryption keys
- Configurable expiry (1 to 720 hours, default 7 days)
- Sender can revoke messages before expiry
- Automatic cleanup of expired encrypted blobs
- Brute-force protection with rate limiting and lockout
Send a secure message
Add secure: true to a standard send request. The response includes the passphrase and a viewer URL.
curl -X POST https://pushmail.dev/api/v1/send \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"siteId": 1,
"subject": "Confidential document",
"html": "<h1>Sensitive information</h1><p>Account number: 12345</p>",
"secure": true,
"expiresInHours": 48
}'{
"data": {
"sendId": 42,
"status": "queued",
"secure": true,
"token": "a1b2c3d4...",
"passphrase": "tiger-castle-seven-river",
"viewUrl": "https://pushmail.dev/secure/a1b2c3d4...",
"expiresAt": "2026-03-03T12:00:00.000Z"
}
}Share the passphrase with the recipient through a separate channel (e.g. SMS, chat, in person). The recipient opens the viewer URL, enters the passphrase, and the message is decrypted in their browser.
Custom passphrase
You can provide your own passphrase instead of using the auto-generated one:
curl -X POST https://pushmail.dev/api/v1/send \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"siteId": 1,
"subject": "Confidential",
"html": "<p>Secret info here</p>",
"secure": true,
"passphrase": "my-custom-passphrase-123"
}'List secure messages
Retrieve all secure messages sent by your organization.
curl "https://pushmail.dev/api/v1/secure-messages?page=1&limit=20" \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"data": {
"messages": [
{
"id": 1,
"token": "a1b2c3d4...",
"sendId": 42,
"siteId": 1,
"contactId": 10,
"senderName": "Acme Corp",
"expiresAt": "2026-03-03T12:00:00.000Z",
"status": "active",
"locked": false,
"viewCount": 2,
"firstViewedAt": "2026-03-01T14:30:00.000Z",
"revokedAt": null,
"createdAt": "2026-03-01T12:00:00.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}
}Get secure message details
curl "https://pushmail.dev/api/v1/secure-messages/1" \
-H "Authorization: Bearer pm_live_YOUR_KEY"Revoke a secure message
Revoke a message before it expires. This permanently deletes the encrypted content from storage and marks the message as revoked. The recipient will see a revocation notice if they try to view it.
curl -X POST "https://pushmail.dev/api/v1/secure-messages/1/revoke" \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"data": {
"message": {
"id": 1,
"token": "a1b2c3d4...",
"status": "revoked",
"revokedAt": "2026-03-01T15:00:00.000Z",
"senderName": "Acme Corp",
"expiresAt": "2026-03-03T12:00:00.000Z"
}
}
}Revocation is irreversible. Once revoked, the encrypted blob is deleted from R2 and cannot be recovered.
Message lifecycle
A secure message goes through the following states:
| Status | Description |
|---|---|
active | Message is available for decryption by the recipient |
expired | Message has passed its expiry time; encrypted blob has been deleted |
revoked | Sender has revoked the message; encrypted blob has been deleted |
Automatic expiry cleanup
A cron job runs every minute to clean up expired messages:
- Finds all
activemessages whereexpiresAtis in the past - Deletes the encrypted blob from R2 storage
- Updates the message status to
expired
This ensures that expired encrypted content is permanently removed from storage and not just logically marked.
Security features
Brute-force protection
- Rate limiting: 5 passphrase attempts per 15 minutes per IP address
- Lockout: After 10 total failed attempts, the message is permanently locked
- Split-key verification: The server verifies the passphrase without ever seeing the encryption key
Zero-knowledge encryption
The server stores:
- A verification hash (to check if the passphrase is correct)
- The encrypted blob (ciphertext)
- Two independent salts (for key derivation)
The server never stores or has access to:
- The passphrase
- The encryption key
- The plaintext content
Dashboard
View and manage secure messages from the Secure Messages section in the dashboard. You can see message status, view counts, expiry dates, and revoke active messages with one click.
Next steps
- Sending -- General sending documentation
- API Reference -- Full endpoint reference