Putting an email provider behind one internal adapter is supposed to make it replaceable. It only does that if the adapter models how the provider actually fails, not just which methods it has.
What changed when the provider changed
I moved a product from one transactional email provider to another. The method names lined up almost one for one. The failure behaviour did not.
The old client threw an exception when a send failed. The new one resolves normally and returns an object that contains either the result or an error. Code written for the first provider would have treated every one of those error responses as a successful send, because nothing was thrown.
That is a quiet failure in exactly the wrong place. A password reset or a transaction notice that was never delivered would have looked delivered.
What the adapter is responsible for
The adapter now turns both kinds of failure into one internal result: a thrown transport error and a returned provider error look the same to the caller. Nothing above the adapter contains a provider-specific branch, and a resolved promise can no longer be mistaken for a delivered message.
What I look for in any provider abstraction
Matching method names is the easy tenth of the work. The rest is deciding what the adapter promises about timeouts, retries, message identifiers, partial success and failure. If those are left to each provider's defaults, the abstraction changes behaviour every time the provider behind it changes.
How I tested it
The useful test is not "a message sends". It is "a provider error becomes a failure the caller can see", run against both response styles.