Build Transmit
A two-pane transfer client that connects over SFTP and to S3-compatible object storage, keeps encrypted bookmarks, queues transfers that survive a dropped connection and resume rather than restart, verifies what it uploaded, and never silently overwrites a newer file.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree if you own one
Why The native panel, keychain and drag and drop are platform APIs.
Get it Apple menu, then About This Mac.
Verify
About This Mac reports macOS 14 or higher - installfree
Why Builds the application and links libssh2.
Get it Mac App Store or developer.apple.com/xcode. open ↗
Verify
xcodebuild -version prints 16 or higher - have readya few USD a month, or free on hardware you own
Why Local testing hides authentication, host key and timeout behaviour. You need somewhere you can also change the host key deliberately in Phase 1.
Get it Any small virtual server with SSH, or a machine on your network. You must be able to regenerate its host key to test the refusal path.
Verify
You can connect and can change the host key on purpose - API keystorage is cheap; some providers charge for requests and egress
Why Phase 3 needs a real bucket. Providers differ subtly in their S3 implementations, so test against the one you will actually use.
Get it Create a bucket with any S3-compatible provider and credentials scoped to it. Confirm it supports multipart upload before relying on it.
Verify
A signed list request returns the bucket contents - have readyfree
Why Resume and multipart both need a file large enough to interrupt partway, which a small file does not give you.
Get it Generate a file comfortably above your multipart threshold. Keep it as a fixture.
Verify
A file large enough to interrupt mid-transfer is available
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- bookmarks(id, name, protocol, host, port, username, remote_path, local_path, keychain_ref) · never a password field - transfers(id, bookmark_id, direction, local_path, remote_path, bytes_total, bytes_done, state, attempts, error, queued_at) - conflicts(transfer_id, reason, local_mtime, remote_mtime, local_size, remote_size, resolution) - verifications(transfer_id, algorithm, local_digest, remote_digest, verified_at) - The queue is persisted, so quitting mid-transfer and relaunching resumes rather than starting again.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
BOOKMARKS_DB_PATH | required | ~/Library/Application Support/Transfer/bookmarks.db | Bookmarks and the transfer queue. Contains no secrets by design. |
HOST_KEYS_PATH | required | ~/Library/Application Support/Transfer/known_hosts | Stored host keys. A mismatch here is a refusal, not a warning. |
MULTIPART_THRESHOLD_MB | required | 64 | Files above this upload in parts. Below it, a single request is simpler and faster. |
TRANSFER_MAX_ATTEMPTS | required | 5 | Retries before a transfer is marked failed with its error preserved. |
VERIFY_AFTER_TRANSFER | required | true | Whether to compare digests after transfer where the protocol allows it. |
The build, in order
Connect and list, with host keys taken seriously
You can browse a remote server, and an unexpected host key stops you rather than warning you.
Key and password authentication, directory listing with sizes and modification times.
Files
SFTP.swiftFirst connection stores; later connections compare. A mismatch is a refusal.
Files
HostKeys.swiftBookmarks store a keychain reference, never a secret. Grep your settings file to prove it.
Files
Credentials.swift
done when · tick each as it passeswatch out- A changed host key is what a machine-in-the-middle attack looks like, and a warning dialogue with a continue button trains people to ignore exactly the signal that matters. Refuse the connection and require a deliberate action in settings to accept a new key, which is what a careful client does.
Transfers that resume
A transfer interrupted at eighty percent continues from eighty percent.
Written to the database as transfers progress, so a crash loses at most a small window.
Files
Queue.swiftCheck the remote size and, where possible, the overlapping bytes. Trusting the offset alone is how corruption happens.
Files
Resume.swiftExponential backoff, a cap, and the error preserved for the user rather than retried forever.
Files
Queue.swift
done when · tick each as it passeswatch out- Resuming an upload by appending without verifying what the server already has will corrupt the file if the partial upload ended mid-write. Check the remote size before resuming and, where the protocol allows, verify the overlapping region rather than trusting the offset.
Object storage as a second protocol
An S3-compatible bucket behaves like a folder, with its differences made visible.
Signing is fiddly and a wrong signature returns an unhelpful error. Test against a known request before building on it.
Files
S3/Sign.swiftAbove a threshold, upload in parts. Abort incomplete uploads on failure, because unfinished parts are billed and invisible.
Files
S3/Multipart.swiftShow the object count before a prefix delete. This is not a directory and the interface should not fully pretend it is.
Files
S3/Delete.swift
done when · tick each as it passeswatch out- Object storage has no directories: a folder is a shared key prefix, and an empty folder does not exist at all. Presenting prefixes as folders is right, but deleting a folder means deleting every object under it, which is not the same operation as removing an empty directory. Say so before performing it.
Conflicts and verification
Nothing is overwritten by accident, and you know the upload arrived intact.
Compare size and modification time. Default to refusing, never to overwriting.
Files
Conflicts.swiftSizes and timestamps for local and remote. The user decides; the client does not guess.
Files
ConflictSheet.swiftA digest comparison where available. Test with a deliberately corrupted transfer to confirm it fails.
Files
Verify.swift
done when · tick each as it passeswatch out- Comparing modification times across systems is unreliable: clocks differ, timezones are handled inconsistently, and some servers report second precision while others report better. Treat a newer remote file as a conflict to surface rather than a fact to act on, and show both timestamps so the user can judge.
Synchronising a folder
You can mirror a local folder to a remote one and know exactly what will change first.
Compare listings, produce uploads, updates and deletions as a plan before doing anything.
Files
Sync.swiftTheir own count and their own confirmation. This is the operation that causes the losses.
Files
SyncSheet.swiftPatterns applied before the plan is built, so excluded files never appear in it.
Files
Sync.swift
done when · tick each as it passeswatch out- A sync with deletion enabled and a mis-set local path will cheerfully empty a remote folder, and the operation is fast. Always dry run first, show the deletion count prominently, and require confirmation specifically for deletions rather than for the sync as a whole.
Signing, notarization and updatesproduct builder
Others can install it, and it handles their credentials responsibly.
Hardened runtime, Developer ID, notarytool, stapler.
terminalxcrun notarytool submit Transfer.zip --keychain-profile AC --wait
Connection details only. Inspect the exported file to confirm.
Files
Export.swiftKeychain storage, host key policy, and what an export contains.
Files
README.md
done when · tick each as it passeswatch out- Exporting bookmarks for backup is a feature people want and a way to leak credentials, because the obvious implementation includes them. Export bookmarks without secrets and say so, so a shared bookmark file is safe to share.
That is the whole plan for Transmit. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The long tail of protocol and server compatibility.
- Mounting remote storage as a local disk.
- Two-way synchronisation with conflict resolution.
- The polish of a client maintained for two decades.
- protocol compatibility, native polish, sync, security, and ongoing maintenance
- years of OS-version edge cases
- large extension ecosystem
- cross-device sync
- commercial support and rapid compatibility fixes
- A menu bar transfer status so long uploads do not need the window open
- Server-side move and copy where the protocol supports it, avoiding a round trip
- Scheduled sync with a notification summarising what changed
Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.