On the whole, I find limiting the surface area of mocks to the least amount of code to work best. An API call to an email service to send our newly created user a welcome email requires making an HTTP connection. Isolating this request to the smallest possible function allows us to mock the least amount of code in tests. Typically, this should be a function with no more than 1–2 lines to make the HTTP connection and return any error along with the response. The same is applicable when publishing an event to Kafka or creating a new user in the database.
… that the code we’re building might probably only be a few hours away from dying (being modified). Thus optimizing for ease of modification of code tends to work better than trying to build topdown narratives of the sort proposed in Clean Code.