Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Sink

Connecting your apps

Updated

An application sends its mail to Sink through the sink mail transport in the artisan-build/sink-client package. The transport posts each message to Sink's /ingest endpoint with a bearer credential that belongs to that application. Sink accepts it, stores it, and labels it with the application it came from.

Attach from Scalpels

Attaching is the general provider flow described in Attach to an existing app. For Sink, attaching a Laravel Cloud environment or Laravel Forge site:

  • mints an ingest credential on your Sink installation for that application;
  • writes SINK_URL (your Sink installation's base URL) and SINK_TOKEN (the new credential) into the application's environment, along with BFC_CLIENT_IDENTITY;
  • opens a draft pull request, on the branch scalpels/connect-sink, that adds artisan-build/sink-client at ^0.2 to composer.json and the variable names to .env.example.

SINK_URL is the base URL only. The transport adds /ingest itself, so never append it.

Check out the branch, run the composer update command shown in the pull request, commit the lock file, then review and merge. The package registers the sink mailer and the sink:install and sink:update commands; it adds no configuration file to your repository.

For an application that runs somewhere Scalpels cannot write to, the provider's connected-apps screen in Scalpels can generate credentials by hand. It shows the composer require line and the environment lines once, ready to paste. Scalpels does not keep the token.

Switch the mailer on

Installing the package does not change where mail goes. Capture starts only when the application selects the sink mailer:

MAIL_MAILER=sink

Set this only in the environments whose mail you want captured, then deploy.

If the application's APP_ENV is production, the transport refuses to start and throws SinkProductionFuse before sending anything. That fuse stops real customer mail from disappearing into Sink by accident. If a production-environment application genuinely should capture rather than deliver, set SINK_ALLOW_PRODUCTION=true deliberately. Do not change APP_ENV to get around it.

How sending behaves

  • Each send waits for Sink. The send returns once Sink has accepted and stored the raw message with HTTP 202. Parsing the subject, recipients, headers, links and attachments happens afterwards on Sink's queue, so a message can be in the inbox a moment before its details are.
  • Failures are loud. A failed request is retried with exponential backoff, three attempts by default. If every attempt fails, the send throws, so a queued mail job fails visibly instead of dropping the message.
  • Retries never double-count. Every send carries its own idempotency key, reused across its retries. Sink stores one message per key, so a retried send is counted once while two genuine sends of identical mail are counted twice.
  • Oversized mail is trimmed, not lost. Above 10,485,760 bytes of raw MIME (the default limit), the client drops attachments; if that is still too large, it sends headers only. The message's Truncation value records which happened: none, attachments_dropped or headers_only.

The app label

Sink labels every message with the subject reference of the credential that sent it. That label is what you filter on in the inbox and in the MCP tools. For a new connection Scalpels creates, the label combines a readable name with the exact hosting identity:

  • a Laravel Cloud environment appears as, for example, Storefront (production) [env-…];
  • a Laravel Forge site appears as, for example, checkout.example.com [forgesite-4101];
  • a credential generated by hand remains manual- followed by a random tag.

The readable part identifies the application to a person; the bracketed identity keeps each managed connection distinct and traceable. Existing credentials keep the subject reference they were issued with, so an older connection continues to show its environment id or forgesite- tag unless you deliberately disconnect and recreate it.

A credential an administrator creates inside Sink uses whatever Subject reference they type, so a readable name such as billing-staging is a good choice there.

Confirm it works

Send a message from the connected application, for example from Tinker:

php artisan tinker --execute='Illuminate\Support\Facades\Mail::raw("hello", fn ($message) => $message->to("[email protected]")->subject("Sink smoke test"));'

The command should exit without an exception. Open the inbox in Sink and you should see Sink smoke test under the application's label.

To check that the client and server agree on the envelope format, run this in the connected application:

php artisan sink:update

A compatible pair prints Your Sink server understands envelope v1. You're good. If the client is newer than the server, the command fails and tells you to update Sink first. Always update the Sink installation before its clients.