A mobile upload progress bar reaching 100% does not prove that an image was attached to its draft. The connection may fail after object storage accepts the bytes but before the backend confirms the relationship. A signed upload URL may also expire while the task is waiting.
Separate selection, transfer and business confirmation. Keep a stable task identity and make retry safe for the same attachment. This avoids treating every network interruption as a completely new upload.
Distinguish transferred bytes from a completed action
Consider a hypothetical notes app that uploads an image and attaches it to a note. The phone, application API and object store have separate responsibilities. Receiving an object does not by itself establish which account and draft may use it.
A presigned URL delegates an operation subject to the provider's conditions and lifetime. Amazon S3 describes these URLs as bearer tokens that should be protected. Do not include the full URL in analytics, public logs or a shareable error message.
Create the task before transmitting the file
One possible API contract starts by creating an upload session. The backend records its owner and intended draft; the client preserves the identifier through retry. These endpoint names illustrate responsibilities rather than prescribing an implementation.
POST /upload-sessions
client_operation_id, draft_id, media_type, expected_size
PUT the URL supplied by the backend
send the file under the signed conditions
POST /upload-sessions/{id}/complete
ask the backend to verify and attach the object
The backend must check ownership rather than trusting a draft identifier supplied by the client. It also needs to validate the expected object and current draft state. A late upload should not silently recreate a draft the person already deleted.
Keep the distinction between a task and a selected image. Replacing an image should establish which task is now current, so an earlier upload cannot overwrite the later choice when its response finally arrives.
Classify failures before retrying
| Observation | Question to answer | Recovery to design |
|---|---|---|
| Connection unavailable | Is the local source still readable? | Preserve the task and wait appropriately |
| Signed request rejected | Expired permission or mismatched conditions? | Ask the backend to inspect and renew when allowed |
| Transfer may have succeeded | Does the expected object exist? | Reconcile before sending again |
| Completion request repeated | Is this session already confirmed? | Return its consistent accepted result |
| Person replaced the image | Does the task still own the selection? | Do not attach an obsolete result |
Do not translate every 403 into URL expiration. Signature conditions, permissions and request headers can also matter. Retain a safe error category and session identifier for diagnosis without exposing the signed credential.
If completion is retried after a lost response, the service should recognize an already completed session. Creating a fresh attachment record each time simply moves the duplicate-delivery problem from the transfer layer to the application database.
Decide whether multipart upload earns its complexity
S3 documents multipart upload as initiation, individual part uploads and completion or cancellation. That can help manage larger transfers, but adds state and cleanup responsibilities. It does not automatically make an application resilient to interruption.
Start with the actual file sizes, mobile platform behavior and network conditions you need to support. A small avatar may not justify a multipart state machine. If you do use one, persist the upload identifier and relevant part state, and define how abandoned transfers are canceled or cleaned up.
Do not leave cleanup solely to whether the person returns to the app. A task can be abandoned permanently, so the storage and backend design need their own retention rules.
Test controlled interruption points
Disconnect before sending, during transfer and after the store accepts the object. Each point tests a different assumption. For the last one, verify that retry produces one intended attachment rather than relying only on the message shown in the interface.
Close the app while the task waits. Change accounts, delete the draft and replace the selected image. The interface should preserve unrelated text, distinguish waiting from source reselection, and explain what retry will attempt.
For Android sources, see Photo Picker URI access for background uploads. For queued changes and operation identity, read outbox synchronization with WorkManager.
Frequently asked questions
Is an HTTP success from object storage the final success condition?
Only if your product contract defines it that way. A flow that subsequently attaches an object to a draft still needs business confirmation and ownership checks.
Does extending the signed URL lifetime solve the problem?
No. It does not repair a missing source, mismatched signature conditions, duplicate completion or a changed draft. Give each state an appropriate recovery path.
Primary sources
Check these original references again before making a release or policy decision.

