Every Salesforce integration starts the same way: someone copies twenty lines of HttpRequest setup into a new class, hard-codes an endpoint, and forgets to handle the timeout. Multiply that by five integrations and you have five slightly different, slightly broken callout implementations. A small callout framework fixes the boilerplate, the security, and the reliability in one place.

The boilerplate problem

A raw Apex callout looks harmless but hides several decisions people get wrong:

HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/v1/contacts'); // hard-coded URL
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + secret);      // secret in code!
req.setHeader('Content-Type', 'application/json');
req.setTimeout(120000);
req.setBody(JSON.serialize(payload));
HttpResponse res = new Http().send(req);                 // no retry, no error handling

The endpoint is hard-coded (so it can't differ per environment), a secret is sitting in Apex, there's no retry on a transient failure, and the response is used without checking the status code. Every one of these is a production incident waiting to happen.

Named Credentials: get secrets out of code

Named Credentials are the single biggest upgrade you can make. They store the endpoint and authentication in Setup, and Salesforce injects the auth header (and refreshes OAuth tokens) for you. Your code references a credential by name and never touches a secret:

req.setEndpoint('callout:Example_API/v1/contacts');

Benefits: no secrets in source control, no Remote Site Settings to maintain, and painless promotion across sandboxes and production — the credential differs per org while the code stays identical.

A fluent request builder

Instead of assembling HttpRequest by hand, wrap it in a builder that reads sensible defaults (base path, headers, timeout) from Custom Metadata and lets callers express intent:

HttpResponse res = MF_Callout.config('Example_API')  // loads endpoint, timeout, headers from CMDT
    .post('/v1/contacts')
    .header('Content-Type', 'application/json')
    .body(JSON.serialize(payload))
    .send();

Now the endpoint, default headers, timeout, and retry policy live in a MF_Callout_Config__mdt record. Changing a timeout is a metadata edit, not a deployment.

Automatic retries — but only when it makes sense

Networks are flaky. A timeout or an HTTP 5xx often succeeds on a second attempt, so the framework retries transient failures up to a configured count. Crucially, it does not retry 4xx client errors — a 400 or 401 won't fix itself, and retrying just wastes time and callout limits:

Integer attempts = 0;
do {
    res = new Http().send(req);
    attempts++;
    if (res.getStatusCode() < 500) break;   // success or client error: stop
} while (attempts < maxRetries);

Note: Apex callouts are synchronous and share the transaction's CPU budget, so keep retry counts small (2–3) and rely on Named Credential timeouts rather than long sleeps between attempts.

A reusable mock makes tests trivial

You can't make real callouts in a unit test, so Salesforce requires an HttpCalloutMock. Writing a bespoke mock per test is tedious; a reusable, configurable mock lets every integration test simulate success, a specific status code, or a transient failure in one line:

Test.setMock(HttpCalloutMock.class,
    new MF_CalloutMock(200, '{"status":"ok"}'));
// ...call your integration code and assert...

// Simulate a flaky endpoint that fails twice then succeeds:
Test.setMock(HttpCalloutMock.class,
    MF_CalloutMock.failThenSucceed(2, 200, '{"status":"ok"}'));

This makes it easy to test the retry logic itself, not just the happy path.

Put it together

The MF Callout Framework accelerator bundles these ideas: a fluent MF_CalloutRequest builder, an MF_CalloutService that reads endpoint, headers, timeout, and retry policy from Custom Metadata and supports Named Credentials, and a reusable MF_CalloutMock for tests. The result is that a new integration is a few lines of intent-revealing code with consistent error handling and reliability baked in.

Frequently asked questions

Why use Named Credentials for callouts?

They store the endpoint and authentication outside your code, so Salesforce handles auth headers and token refresh, secrets never live in Apex, and you skip Remote Site Settings — while promoting across environments without code edits.

How do you retry a failed callout in Apex?

Inspect the response and re-send on transient failures (timeouts, HTTP 5xx) up to a configured limit. Don't retry 4xx client errors — they won't succeed on retry.

How do you unit test an Apex callout?

Implement HttpCalloutMock to return canned responses and register it with Test.setMock. A reusable mock lets you simulate success, specific status codes, and transient failures across every integration test.