Designing an International Money Transfer System | by Anurag A S | Coffee☕ And Code💚 | Jun, 2026 | MediumSitemapOpen in appSign up<br>Sign in
Medium Logo
Get app<br>Write
Search
Sign up<br>Sign in
Coffee☕ And Code💚
Where coding meets caffeine. Fueling developers with code, creativity, and a dash of inspiration
Designing an International Money Transfer System
Anurag A S
10 min read·<br>5 hours ago
Listen
Share
How I’d design a cross-border transfer platform that can quote FX, reserve funds, survive retries, integrate with asynchronous payment rails, and remain auditable when things go wrong.<br>Press enter or click to view image in full size
Photo by Tech Daily on Unsplash
International transfers look like a straightforward workflow: debit one customer, convert currency, credit another. That mental model is wrong in the ways that matter.<br>A transfer is a long-running financial process spanning multiple systems we do not control: FX providers, screening vendors, correspondent banks, domestic rails, and notification providers. The user expects one definitive answer to “where is my money?” while the platform has only partial, delayed information.<br>The design problem is therefore not simply moving money. It is preserving financial truth while coordinating unreliable external work.<br>This is the design I would use for a Wise- or Remitly-style product. I will deliberately separate the transfer workflow from the ledger , FX , and rail adapters . That boundary is the difference between an understandable system and one service that owns every failure mode.
1. Frame the Problem Correctly<br>The system accepts a sender-funded transfer in one currency and delivers a beneficiary amount in another. It must:<br>give the customer an executable FX quote;<br>check eligibility, sanctions, and risk before money leaves the platform;<br>reserve and then settle the sender’s funds exactly once;<br>route the payout to an appropriate rail and track its outcome;<br>record immutable accounting entries and state transitions; and<br>support cancellation, repair, reconciliation, and customer-visible status.<br>It does not own bank accounts, become a correspondent bank, or assume that every payment rail behaves synchronously. Those are capabilities supplied by a regulated entity and its partners; their contracts define the actual payout semantics.<br>The invariant that drives the design<br>There are two kinds of truth:<br>Internal financial truth — balances, reservations, fees, FX positions, and postings. This must be strongly consistent and append-only.<br>External delivery truth — whether a rail accepted, processed, recalled, rejected, or credited a payment. This is asynchronous and sometimes uncertain.<br>Trying to make the second as synchronous as the first produces distributed transactions across banks and payment networks. That is not viable. Instead, the system makes internal actions atomic, then models external work as an idempotent state machine with explicit uncertainty.
2. Requirements<br>Functional requirements<br>Create a locked quote for a source amount, target currency, fee, and expiry.<br>Initiate a transfer using that quote and an idempotency key.<br>Reserve sender funds before asynchronous checks and rail submission.<br>Screen parties and payment data; allow pass, reject, and manual-review outcomes.<br>Route payouts to a rail based on corridor, currency, amount, cost, and availability.<br>Track the transfer from creation through final settlement or repair.<br>Allow cancellation only while the funds have not irreversibly left the platform.<br>Preserve an immutable ledger and an audit trail for every decision and state change.<br>Non-functional requirements<br>Correctness : No duplicate internal posting or payout instruction for a logical transfer.<br>Transfer creation latency : p99 under 500 ms, excluding user interaction and external settlement.<br>Availability : 99.99% for reads and transfer creation; safely pause new rail submissions during dependency incidents.<br>Durability : Acknowledged transfer and posting data must survive an availability-zone failure.<br>Scale : Support 1M transfers/day initially; scale writes by transfer ID and corridor rather than a global lock.<br>Auditability : Reconstruct the complete transfer lifecycle and accounting position from durable records.<br>A deliberate non-goal<br>“Exactly once” cannot be guaranteed end-to-end when an external rail times out after receiving a request. The correct guarantee is more precise: exactly-once internal accounting, and effectively-once external submission through stable partner references and reconciliation . A timeout is not a failure; it is an UNKNOWN outcome until proved otherwise.
3. Core Domain Model<br>The model should make money, workflow state, and side effects independently visible.<br>Transfer<br>transfer_id UUID PK<br>idempotency_key VARCHAR<br>request_fingerprint CHAR(64)<br>sender_id UUID<br>recipient_id UUID<br>source_amount DECIMAL(19,4)<br>source_currency CHAR(3)<br>target_amount DECIMAL(19,4)<br>target_currency CHAR(3)<br>quote_id UUID<br>state ENUM<br>rail_provider...