In development ·Join early access
Engineering guide

Coordinating payment and email API integrations

A fictional application separates payment confirmation, incoming events, durable business state, and email delivery without assuming a cross-provider transaction.

Coordinate business state without assuming a remote transaction

A fictional application confirms an external payment and then sends a transactional notification. The payment provider and email provider are separate systems. A shared integration layer can operate both connections, but it cannot make their side effects one atomic transaction.

Keep payment decisions in application code. Integration infrastructure handles authorized requests, incoming delivery, attempts, redacted inspection, and controlled recovery. This article is an architecture example, not a checkout implementation, financial advice, or a claim that Invokation has live payment connectors.

Stripe and SendGrid are recognizable examples of these dependencies. The application operations below are fictional contracts; they are not native provider endpoint names or a universal payment API.

Separate four pieces of evidence

A user initiating an action, a provider accepting a request, an authoritative payment-state observation, and an email being delivered are different events. Decide what evidence your business workflow requires before moving to the next state.

For example, Stripe’s webhook documentation describes asynchronous events and cautions about duplicates and ordering. SendGrid’s Mail Send reference describes an accepted dispatch response. Neither observation establishes an atomic relationship between a payment and an email.

A useful application model might contain payment_pending, payment_confirmed, notification_pending, and notification_dispatched. These are illustrative local states, not prescribed provider statuses. If final message delivery matters, track that separately from dispatch and define how later delivery evidence is reconciled.

An illustrative event sequence

  1. The application records a pending business action with its own stable identifier.
  2. A payment interaction occurs through the application’s chosen provider flow.
  3. An authenticated incoming event or permitted authoritative read provides relevant payment evidence.
  4. The application validates the transition against current state and business policy.
  5. A database transaction commits the state change and an outbox intent for the notification.
  6. A worker dispatches the email through a configured connection profile.
  7. Dispatch acceptance and later delivery evidence, if available, update separate notification status.
  8. Monitoring connects these steps using safe correlation references and explicit outcomes.

The outbox prevents a crash between committing local business state and recording that a notification is needed. It does not by itself prevent duplicate remote dispatch after a worker crash. The destination’s supported idempotency or a reconciliation policy still matters.

Keep the contract application-owned

The backend can express a read through an interface such as:

// Application-owned adapter interface, independent of Invokation.
const observation = await payments.getStatus({ paymentId });
await database.transaction(async (tx) => {
  const action = await tx.actions.get(actionId);
  if (!policy.mayConfirm(action, observation)) return;
  await tx.actions.confirmOnce(actionId, observation.reference);
  await tx.outbox.insertOnce(`notification:${actionId}`, {
    kind: "transactional_notification",
    actionId,
  });
});

The transaction and confirmOnce methods are pseudocode. Their implementation must protect the relevant application invariant under concurrent events. Do not copy the method names and assume they supply uniqueness or remote idempotency automatically.

The application owns interpretation of payment state. An adapter should preserve unknown or unsupported provider states rather than mapping everything into “success” and “failure.” A later reversal or another domain event may require a different application transition; the integration layer cannot infer those business rules.

A failure matrix for the review

SituationEvidence to inspectAppropriate next actionUnsafe shortcut
Duplicate incoming eventReceipt key and existing transitionAcknowledge durable receipt; avoid repeating committed transitionSend another email because another event arrived
Delayed eventCurrent state and supported provider version/evidenceApply only an allowed transition or reconcileOverwrite newer state with the last payload received
Email dispatch failureAttempt category and notification intentRetry only eligible dispatch; preserve local payment stateRepeat the payment to restart the workflow
Ambiguous payment responseStable action reference and authoritative provider stateReconcile before another side effectTreat a timeout as proof the payment did not happen
Accepted email, missing delivery evidenceProvider’s documented lifecycle and event visibilityPreserve uncertainty; use the application’s deadline policyCall acceptance an exactly-once inbox delivery

This matrix is useful even with a single direct integration module. Its purpose is to make ambiguity explicit before an incident creates pressure to click “retry.”

Authentication is connection work

Store authorized server-side credential references per environment. The payment connection and email connection have different permissions, account boundaries, and rotation procedures. Both can use the same execution vocabulary without sharing credential values or destination semantics.

The proposed Invokation interface uses an Invokation backend key to authorize a profile. The profile handles its downstream authentication. For the named examples here, Stripe documents API keys and SendGrid documents API-key authentication. The separate fictional custom API elsewhere on this site demonstrates an expiring token endpoint; that flow is not attributed to either provider.

Observe the whole workflow without copying sensitive data

Correlate application action, incoming receipt, integration run, and notification intent. Keep raw payment details, recipient addresses, credentials, and full provider payloads out of generic telemetry. Prefer protected references and redacted categories. Define retention for each record rather than retaining an unrestricted event archive indefinitely.

Separate provider-account outages from one connection’s revoked credential. An email backlog should not block payment-status reads through an unrelated queue. Bound retry counts and total age, and give manual review an owner who understands the application transition.

Test the boundaries that matter

Use synthetic events and isolated accounts. Test duplicate and out-of-order delivery, a worker crash after remote dispatch, a timeout with an unknown payment outcome, unavailable storage before acknowledgment, and a revoked credential. Verify local business invariants and explicit uncertainty rather than only counting HTTP successes.

Start with the webhook lifecycle guide and controlled replay guide. The API centralization use case shows how these distinct operations fit one operating model, while the developer preview keeps requests, queue acceptance, and downstream credentials separate.

In development · Shape the first product

Bring a real integration problem.

Share the constraints your team keeps meeting. No payment or commitment required.

Join early access