PushMail.dev

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.

Send a secure message
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
  }'
Response (202)
{
  "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:

Custom passphrase
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.

List secure messages
curl "https://pushmail.dev/api/v1/secure-messages?page=1&limit=20" \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Response
{
  "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

Get 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.

Revoke a message
curl -X POST "https://pushmail.dev/api/v1/secure-messages/1/revoke" \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Response
{
  "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:

StatusDescription
activeMessage is available for decryption by the recipient
expiredMessage has passed its expiry time; encrypted blob has been deleted
revokedSender has revoked the message; encrypted blob has been deleted

Automatic expiry cleanup

A cron job runs every minute to clean up expired messages:

  1. Finds all active messages where expiresAt is in the past
  2. Deletes the encrypted blob from R2 storage
  3. 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

On this page