Attachments (Customer-Managed Storage)
Send and receive end-to-end encrypted attachments using your own bucket. DropOnAir never stores or proxies file bytes — the SDK uploads and downloads directly against your storage via short-lived presigned URLs we mint for each request.
How it works
- Configure your bucket once in the DropOnAir panel (S3-compatible, GCS, or Azure Blob).
- Your app calls
prepareAttachmentAndUpload(file). The SDK generates a random AES-256-GCM file key, encrypts the bytes, asks DropOnAir for a presigned PUT URL, uploads directly to your bucket, and finalizes with the integrity hash. - The SDK wraps the file key per recipient device using the same X25519 + HKDF path used for messages. DropOnAir never sees the unwrapped key.
- You attach the returned
AttachmentRefto a message:sendMessage(recipient, text, { attachments }). - The recipient SDK receives the message, asks DropOnAir for a presigned GET, fetches bytes from your bucket, unwraps the file key locally, decrypts.
Supported storage adapters (v1)
- S3-compatible: AWS S3, Cloudflare R2, MinIO, Backblaze B2, Wasabi (via an optional endpoint override).
- Google Cloud Storage (V4 signed URLs).
- Azure Blob Storage (Service SAS).
Plan availability
Per-file size and per-month count limits are enforced server-side before any presigned URL is issued. Availability and exact limits depend on your plan, see the pricing page for tiers, or your DropOnAir dashboard Subscription page to see what's enabled on your app.
Webhook events
attachment.upload_session_issued— a client reserved an upload URL.attachment.uploaded— the client finalized the upload, fires with the SHA-256 integrity hash (useful for malware-scan pipelines).attachment.downloaded— a recipient requested a download URL.attachment.revoked— the sender revoked an attachment.
Example
// Encrypt + upload to your bucket + finalize. Returns an AttachmentRef.
const ref = await client.prepareAttachmentAndUpload(fileBytes, {
toUserId: 'bob',
mimeType: 'image/jpeg',
encryptionType: 'E2EE',
});
await client.sendMessage('bob', 'Here is the photo', { attachments: [ref] });
client.onMessage(async (msg) => {
for (const att of msg.attachments ?? []) {
const dl = await client.downloadAttachment(att);
// dl.bytes (Uint8Array), dl.mimeType, dl.sizeBytes, dl.sha256
}
});Optional thumbnails
Pass an optional thumbnail (bytes) alongside the main file. The SDK uploads it as a separate, independently encrypted attachment and links the two via thumbnailAttachmentId on the returned ref, so the recipient can fetch a small preview before the full file. Generating the thumbnail is entirely your app's choice — the platform never resizes, transcodes, or inspects your bytes. Omit the field for no thumbnail.
Revoking an attachment
The original sender can call revokeAttachment(attachmentId). The platform stops issuing download URLs for it (later requests get 410 Gone) and online recipients receive an ATTACHMENT_REVOKED event. Revocation is a server-side access cut, not a recall: bytes a recipient already downloaded cannot be pulled back. Revoking is your app's decision — the platform never revokes on your behalf.
A preview thumbnail is a separate attachment, so revoking a bare attachment id leaves its thumbnail downloadable. Each SDK also accepts the attachment reference (revokeAttachment(ref), since SDK 0.13.1) and revokes the linked thumbnail in the same call. Revoke the thumbnail's own id directly if you only have the id form.
Security model
- DropOnAir never receives, stores, or proxies file bytes.
- The per-attachment AES-256-GCM file key is wrapped per recipient device using X25519 ECDH + HKDF-SHA256 with a context distinct from the message-payload HKDF.
- Storage credentials are AES-256-GCM encrypted at rest in DropOnAir's panel database using the same master key that protects webhook signing secrets. Raw credentials are never returned by the dashboard API.
- Upload URLs have a 15-minute TTL; download URLs have a 5-minute TTL. Download authorization checks that the requester is the original sender or one of the captured recipients.
Requires SDK 0.5.0+ on JavaScript, Android, and iOS (PROTOCOL_VERSION 4). Verify the platform supports it by calling GET /api/info and checking that features includes "attachments".