Skip to content

OutboxSender sends attachments as standalone envelopes #6067

Description

@linear-code

What's happening?

When OutboxSender (sentry-java/Android) processes envelopes written to disk by sentry-native (e.g. after an NDK crash), it iterates each envelope item individually. Event and Transaction items go through captureEvent/captureTransaction, but any other item type — including Attachment — is wrapped in a brand-new single-item SentryEnvelope and sent standalone via captureEnvelope, instead of being sent together with the event that produced it.

Why is it happening? (root cause)

This is OutboxSender.processEnvelope in sentry/src/main/java/io/sentry/OutboxSender.java. The loop dispatches Event/Transaction items via their dedicated capture calls, and falls through to a generic else branch for every other item type that wraps that single item in its own envelope and sends it immediately (around OutboxSender.java#L124, current captureEnvelope(newEnvelope, hint) call). There is no step that buffers non-event items and reassembles them into one outgoing envelope keyed by event_id before sending.

Possible solutions (proposal, not yet decided)

  • Buffer Attachment-type items encountered while iterating envelope.getItems(), converting each SentryEnvelopeItem into an Attachment (filename/content-type from the item header, bytes from item.getData()).
  • When the Event or Transaction item is found, call hint.addAttachments(buffered) before captureEvent/captureTransaction, instead of sending each attachment through its own captureEnvelope call. Hint already supports addAttachments(List<Attachment>), so this doesn't require a new envelope format.
  • Handle item ordering (attachments may appear before or after the event item within the envelope) and the edge case of attachment-only envelopes (no event/transaction present), which must still be sent standalone.
  • Add an explicit size check before merging: if attaching would push the combined envelope over the max envelope size, fall back to sending that attachment standalone (or drop it) rather than risking the whole event getting rejected.
  • Only merge Attachment-type items — leave other non-event item types (session, user feedback, etc.) on their current per-item send path to avoid changing unrelated behavior.
  • Extend OutboxSenderTest.kt to cover reassembly, mixed item ordering, and the no-event fallback.
  • Estimated scope: mostly contained to OutboxSender.java and its test, no protocol/API changes, no server-side work required — ingestion already accepts combined event+attachment envelopes (used by JS and other SDKs today) and will keep supporting standalone attachments for legacy SDK versions regardless.

User Impact

  • Quota/cost impact: Relay has no filter stage for standalone attachments (only processing/errors/ has one), so when an event is dropped by an inbound filter, its standalone attachment still gets ingested and billed — landing as an orphan the customer can't even see, since there's no event to attach it to. Riot Games hit this concretely, ingesting roughly 10TB/day of attachments with ~90% having no associated event, and a live investigation (INGEST-1167) found Android/Java is currently the SDK with the highest volume of standalone attachments. Ingest is shipping a server-side stopgap (discard orphaned attachments at the filter stage), but fixing OutboxSender removes the root cause on the Android/Java side rather than relying on ingest to keep mopping up after it.
  • Mainly affects Android apps using sentry-native (NDK crash reporting) and hybrid SDKs (Unity, Flutter, React Native, etc.) layered on top of sentry-android, since they're the ones writing envelopes to the outbox folder that OutboxSender later fans out.

History of the issue

The design is incidental, not deliberate. The original envelope-pickup code (getsentry/sentry-android#89, getsentry/sentry-android#145, 2019) only handled Event items; every other item type was simply logged as ignored, with a // TODO: Handle attachments and other types comment left in place. In January 2021, getsentry/sentry-java#1158 ("OutboxSender supports all envelope item types") closed that TODO by adding the current catch-all branch: wrap any non-event item in its own envelope and send it immediately. That was a pragmatic fix to stop dropping items, not a considered protocol decision — there's no historical discussion of envelope size limits, item ordering, or ingestion semantics tied to it.

Other useful context

This surfaced from a cross-SDK discussion (also here) about disallowing standalone attachments at the protocol/spec level (develop-docs). Ingestion will keep accepting standalone attachments indefinitely to support already-shipped legacy SDK versions, so this fix is about sentry-java emitting the preferred combined-envelope format going forward, not a breaking or urgent change. If the spec formally disallows standalone attachments before this is fixed, OutboxSender's native-outbox path should get an explicit documented exception until this issue is resolved.

Action taken on behalf of Adam Brown.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions