Bento Laravel Guide

Use `php artisan bento:install` to capture keys, publish configuration, and start tracking events, managing subscribers, and delivering transactional email directly from Laravel.

Install Bento's Laravel SDK, run the artisan installer, and keep events, subscribers, and transactional emails flowing through your PHP apps.

Getting Started

Step 1

Install the Bento Laravel SDK

Install the SDK with your framework’s package manager and keep it alongside your other HTTP clients so configuration, logging, and credentials stay centralized.

Using Composer

bash
composer require bentonow/bento-laravel-sdk

Step 2

Configure the client

Store your Site UUID, publishable key, and secret key in environment variables or a secrets manager. Register the Bento services during your app’s bootstrap so every controller, job, or command reuses the same client.

Prompt-Based Setup

bash
php artisan bento:install

Publish the configuration

bash
php artisan vendor:publish --tag="bentonow"

Configure your .env file

bash
BENTO_PUBLISHABLE_KEY=your_publishable_key
BENTO_SECRET_KEY=your_secret_key
BENTO_SITE_UUID=your_site_uuid

Import the Facade

php
use Bentonow\BentoLaravel\Facades\Bento;

Beginner Guide

Tracking your first event

Events are the fastest way to add subscribers, kick off automations, and capture context in a single API call. Track onboarding milestones, purchases, or page views, then personalize with Liquid.

  • Pair every event with subscriber metadata so flows can branch without extra imports.
  • Include transactional context (cart items, amounts, IDs) for downstream personalization.
  • Prefer events over bespoke subscriber mutations—automations can add tags or update fields later.
Detail Liquid tag
Product details {{ event.details.product.size }}
Purchase amount {{ event.details.value.amount }}
Transaction ID {{ event.details.unique.key }}
Payment method {{ event.details.value.payment_method }}

Track a simple page view

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
    type: '$pageView',
    email: 'user@example.com',
    details: [
        'url' => '/home',
        'title' => 'Home Page',
    ]
);

Bento::trackEvent(collect([$event]));

Track a form submission

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
    type: '$formSubmitted',
    email: 'user@example.com',
    details: [
        'form_name' => 'Newsletter Signup',
        'source' => 'Homepage',
    ]
);

Bento::trackEvent(collect([$event]));

Managing subscribers

You can still create, tag, or unsubscribe subscribers directly when you need to correct data or build admin tooling. Keep these calls reserved for operational work—events remain the preferred entry point.

Create a new subscriber

php
use Bentonow\BentoLaravel\DataTransferObjects\CreateSubscriberData;
use Bentonow\BentoLaravel\Facades\Bento;

$subscriber = new CreateSubscriberData(
  email: 'new@example.com'
);

Bento::createSubscriber($subscriber);

Tag a subscriber

php
use Bentonow\BentoLaravel\DataTransferObjects\CommandData;
use Bentonow\BentoLaravel\Enums\Command;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$command = new CommandData(
  command: Command::ADD_TAG,
  email: 'user@example.com',
  query: 'Newsletter'
);

Bento::subscriberCommand(collect([$command]));

Unsubscribe A subscriber

php
use Bentonow\BentoLaravel\DataTransferObjects\CommandData;
use Bentonow\BentoLaravel\Enums\Command;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$command = new CommandData(
  command: Command::UNSUBSCRIBE,
  email: 'user@example.com',
  query: null
);

Bento::subscriberCommand(collect([$command]));

Common use cases

  • Track onboarding, purchase, and lifecycle events so automations fire with one API call.
  • Tag subscribers in response to events rather than separate API loops.
  • Store contextual data (cart contents, plan tier) for segmentation and personalization.

Tracking a user login "Event"

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
  type: '$login',
  email: 'user@example.com',
  details: [
    'method' => 'password',
    'device' => 'mobile',
  ]
);

Bento::trackEvent(collect([$event]));

Updating user information

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
  type: '$activation',
  email: 'user@example.com',
  details: [
    'account' => 'active',
    'device' => 'mobile',
  ]
);

Bento::trackEvent(collect([$event]));

Adding multiple tags to a subscriber

php
$subscribers = collect([
  new ImportSubscribersData(
    email: 'user@example.com',
    firstName: null,
    lastName: null,
    tags: ['Premium', 'Annual Plan', 'Early Adopter'],
    removeTags: [],
    fields: []
  )
]);

Bento::importSubscribers($subscribers);

Intermediate Guide

Custom fields and tags

Combine fields for rich profile data with namespaced tags for segmentation. Tags keep audiences organized (`plan:pro`, `status:vip`) while fields store free-form values like locale or favorite product.

Namespaced tag ideas

subscription:basicsubscription:prosubscription:enterprise

Apply via automations whenever possible so marketing and data teams stay in sync.

Create a new custom field definition

php
use Bentonow\BentoLaravel\DataTransferObjects\CreateFieldData;
use Bentonow\BentoLaravel\Facades\Bento;

$field = new CreateFieldData(
  key: 'membershipLevel'
);

Bento::createField($field);

Get all existing fields

php
use Bentonow\BentoLaravel\Facades\Bento;

$fields = Bento::getFields();

Create a new tag

php
use Bentonow\BentoLaravel\DataTransferObjects\CreateTagData;
use Bentonow\BentoLaravel\Facades\Bento;

$tag = new CreateTagData(
  name: 'Power User'
);

Bento::createTag($tag);

Get all existing tags

php
use Bentonow\BentoLaravel\Facades\Bento;

$tags = Bento::getTags();

Tracking purchase events

Always include a `unique` key (order ID, cart token, etc.) so Bento dedupes purchases. Capture cart contents and totals to power LTV reporting and Liquid personalization.

Track a purchase event to monitor customer lifetime value

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
    type: '$purchase',
    email: 'customer@example.com',
    details: [
        'unique' => [
            'key' => 'order-123' // Unique order identifier
        ],
        'value' => [
            'amount' => 9999, // Amount in cents
            'currency' => 'USD'
        ],
        'cart' => [
            'items' => [
                [
                    'product_id' => 'prod-456',
                    'product_name' => 'Premium Widget',
                    'product_price' => 9999,
                    'quantity' => 1,
                    'product_sku' => 'SKU-456',
                ]
            ]
        ]
    ]
);

Bento::trackEvent(collect([$event]));

Laravel middleware

The Bento SDK ships a middleware that strips UTM tracking parameters from signed URLs before Laravel validates the signature—perfect for password resets, email verification, and other secure flows.

Bento adds UTM parameters (utm_source, utm_medium, etc.) to email links for analytics. Those parameters can invalidate signed routes, so clean the URLs before signature checks run.

Register the middleware where you configure global or aliased middleware, then apply it to any routes that rely on signed URLs.

Example signed URLs

  • https://example.com/password/reset/token?expires=1679363200&signature=xxxxxxxxxxxx
  • https://example.com/email-verification?expires=1699999999&signature=abc123def456

Laravel 9 & 10 (Kernel.php)

laravel_ten

php
protected $middleware = [
    // ...
    \Bentonow\BentoLaravel\Http\Middleware\BentoSignatureExclusion::class,
];

Laravel 11+ (bootstrap/app.php)

laravel_eleven

php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'bento' => BentoSignatureExclusion::class
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

Apply to signed routes

apply_a_middleware

php
Route::get('/password/reset/{token}', function () {
    // ...
})->middleware(['bento', 'auth', 'signed']);

Namespaced tags

Reserve tags for segmentation keys (e.g., `product:analytics`). Use fields when you need arbitrary values or timestamps. This keeps your marketing taxonomy predictable.

Advanced Guide

Batch operations

Batch endpoints cover more than 80% of API work. Keep payloads lean, batch 200–300 records, and retry only failed chunks.

Import multiple subscribers at once

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscribers = collect([
    new ImportSubscribersData(
        email: 'user1@example.com',
        firstName: 'Alice',
        lastName: null,
        tags: null,
        removeTags: null,
        fields: ['company' => 'Acme Inc']
    ),
    new ImportSubscribersData(
        email: 'user2@example.com',
        firstName: 'Bob',
        lastName: null,
        tags: null,
        removeTags: null,
        fields: ['company' => 'Beta Corp']
    ),
    // ... up to 1,000 subscribers
]);

Bento::importSubscribers($subscribers);

Import multiple events at once

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$events = collect([
    new EventData(
        type: '$login',
        email: 'user@example.com',
        fields: ['date' => '2023-01-01']
    ),
    new EventData(
        type: '$purchase',
        email: 'user@example.com',
        details: [
            'unique' => ['key' => 'order-123'],
            'value' => ['currency' => 'USD', 'amount' => 9999]
        ]
    ),
    // ... up to 1,000 events
]);

Bento::trackEvent($events);

Transactional emails

Great candidates

  • Onboarding confirmation or welcome sequences
  • Password reset and login verification links
  • Payment, receipt, or fulfillment notices
  • Account or compliance notifications

Avoid using transactional for

  • CC / BCC workflows or multi-recipient fan-out
  • Attachments (link to files instead)
  • Marketing, promotional, or newsletter content
  • Bulk announcements where unsubscribes must be honored

Send a transactional email

php
// Laravel provides a native way to send emails through Bento
// First, configure your .env file to use Bento as a mail driver:
// MAIL_MAILER=bento

// Then, use Laravel's built-in Mail facade:
use Illuminate\Support\Facades\Mail;

// You can use a Laravel Mailable class:
Mail::to('recipient@example.com')
    ->from('Your Name <sender@example.com>')
    ->send(new OrderShippedMailable($order));

// Example Mailable class:

class OrderShippedMailable extends Mailable
{
    public $order;

    public function __construct($order)
    {
        $this->order = $order;
    }

    public function build()
    {
        return $this->subject('Your order #' . $this->order->id . ' has shipped!')
                    ->view('emails.orders.shipped');
    }
}

Subscriber updates

Events remain the preferred way to create and update subscribers—they trigger flows and can mutate tags or fields downstream. Use direct endpoints when workflows demand determinism.

Events-first creation

Create a subscriber when they sign up

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
  type: '$subscribe',
  email: 'new-user@example.com',
  fields: [
    'firstName' => 'Jane',
    'lastName' => 'Doe',
    'signupSource' => 'website'
  ]
);

Bento::trackEvent(collect([$event]));

Create a subscriber when they make a purchase

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
  type: '$purchase',
  email: 'customer@example.com',
  details: [
    'unique' => ['key' => 'order-123'],
    'value' => ['amount' => 9999, 'currency' => 'USD']
  ],
  fields: [
    'first_name' => 'John',
    'last_name' => 'Smith',
    'customerType' => 'new'
  ]
);

Bento::trackEvent(collect([$event]));

Direct creation options

Create a single subscriber (email only)

php
use Bentonow\BentoLaravel\DataTransferObjects\CreateSubscriberData;
use Bentonow\BentoLaravel\Facades\Bento;

$subscriber = new CreateSubscriberData(
  email: 'user@example.com'
);

Bento::createSubscriber($subscriber);

Import multiple subscribers

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscribers = collect([
  new ImportSubscribersData(
    email: 'user1@example.com',
    firstName: null,
    lastName: null,
    tags: null,
    removeTags: null,
    fields: [
      'membershipTier' => 'gold',
      'accountStatus' => 'active',
      'lastRenewalDate' => now()->toDateTimeString()
    ]
  ),
  new ImportSubscribersData(
    email: 'user2@example.com',
    firstName: null,
    lastName: null,
    tags: null,
    removeTags: null,
    fields: [
      'membershipTier' => 'silver',
      'accountStatus' => 'pending',
      'trialEndsAt' => now()->addDays(30)->toDateTimeString()
    ]
  )
]);

Bento::importSubscribers($subscribers);

Event-driven profile updates

Update subscriber when they update their profile

php
use Bentonow\BentoLaravel\DataTransferObjects\EventData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$event = new EventData(
    type: '$subcription_change',
    email: 'user@example.com',
    fields: [
        'subscriptionTier' => 'premium',
    ]
);

Bento::trackEvent(collect([$event]));

Single attribute tweaks

Add or update a single field

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscribers = collect([
  new ImportSubscribersData(
    email: 'user2@example.com',
    firstName: 'Jesse',
    lastName: 'Bento',
    tags: ['membership:premium'],
    removeTags: ['membership:silver'],
    fields: [
      'membershipTier' => 'premium',
      'accountStatus' => 'pending',
      'trialEndsAt' => now()->addDays(30)->toDateTimeString()
    ]
  )
]);

Bento::importSubscribers($subscribers);

Add a tag

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscribers = collect([
  new ImportSubscribersData(
    email: 'user2@example.com',
    firstName: 'Jesse',
    lastName: 'Bento',
    tags: ['example:tag'],
    removeTags: null,
    fields: []
  )
]);

Bento::importSubscribers($subscribers);

Remove a field

php
use Bentonow\BentoLaravel\DataTransferObjects\CommandData;
use Bentonow\BentoLaravel\Enums\Command;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$command = new CommandData(
  command: Command::REMOVE_FIELD,
  email: 'user@example.com',
  query: 'temporaryStatus'
);

Bento::subscriberCommand(collect([$command]));

Batch updates

Update multiple subscribers

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscribers = collect([
  new ImportSubscribersData(
    email: 'user1@example.com',
    firstName: null,
    lastName: null,
    tags: null,
    removeTags: null,
    fields: [
      'membershipTier' => 'gold',
      'accountStatus' => 'active',
      'lastRenewalDate' => now()->toDateTimeString()
    ]
  ),
  new ImportSubscribersData(
    email: 'user2@example.com',
    firstName: null,
    lastName: null,
    tags: null,
    removeTags: null,
    fields: [
      'membershipTier' => 'silver',
      'accountStatus' => 'pending',
      'trialEndsAt' => now()->addDays(30)->toDateTimeString()
    ]
  )
]);

Bento::importSubscribers($subscribers);

Specialized operations

Change a subscribers email address

php
use Bentonow\BentoLaravel\DataTransferObjects\CommandData;
use Bentonow\BentoLaravel\Enums\Command;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$command = new CommandData(
  command: Command::CHANGE_EMAIL,
  email: 'old@example.com',
  query: 'new@example.com'
);

Bento::subscriberCommand(collect([$command]));

Update all fields at once

php
use Bentonow\BentoLaravel\DataTransferObjects\ImportSubscribersData;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$subscriber = new ImportSubscribersData(
  email: 'user@example.com',
  firstName: 'Updated',
  lastName: 'Name',
  tags: null,
  removeTags: null,
  fields: [
    'address' => [
      'street' => '123 Main St',
      'city' => 'New York',
      'state' => 'NY',
      'zip' => '10001'
    ],
    'preferences' => [
      'theme' => 'dark',
      'notifications' => true
    ]
  ]
);

Bento::importSubscribers(collect([$subscriber]));

Unsubscribe a user

php
use Bentonow\BentoLaravel\DataTransferObjects\CommandData;
use Bentonow\BentoLaravel\Enums\Command;
use Bentonow\BentoLaravel\Facades\Bento;
use Illuminate\Support\Collection;

$command = new CommandData(
  command: Command::UNSUBSCRIBE,
  email: 'user@example.com',
  query: null
);

Bento::subscriberCommand(collect([$command]));

Utility features

Validate addresses, guess gender, geolocate IPs, or check blacklists without integrating third-party APIs. These helpers are rate-limited—cache responses when you can.

Validate Email

php
use Bentonow\BentoLaravel\DataTransferObjects\ValidateEmailData;
use Bentonow\BentoLaravel\Facades\Bento;

$data = new ValidateEmailData(
  emailAddress: 'user@example.com',
  fullName: null,
  userAgent: 'Mozilla/5.0...',
  ipAddress: '192.168.1.1'
);

$validationResult = Bento::validateEmail($data);

Guess Prediction (for personalization)

php
use Bentonow\BentoLaravel\DataTransferObjects\GenderData;
use Bentonow\BentoLaravel\Facades\Bento;

$data = new GenderData(
  fullName: 'Alex'
);

$genderInfo = Bento::getGender($data);

Geolocate IP Address

php
use Bentonow\BentoLaravel\DataTransferObjects\GeoLocateIpData;
use Bentonow\BentoLaravel\Facades\Bento;

$data = new GeoLocateIpData(
  ipAddress: '208.67.222.222'
);

$location = Bento::geoLocateIp($data);

Check Domain/IP blacklist

php
use Bentonow\BentoLaravel\DataTransferObjects\BlacklistStatusData;
use Bentonow\BentoLaravel\Facades\Bento;

$data = new BlacklistStatusData(
  domain: 'example.com',
  ipAddress: null
  // Or check an IP instead: domain: null, ipAddress: '192.168.1.1'
);

$blacklistInfo = Bento::getBlacklistStatus($data);

API Reference

The Laravel SDK mirrors Bento's REST resources—most services expose async methods for batch operations plus convenience helpers for validation.

Events & utility

  • Track custom events

    Bento::trackEvent(collect([$event]))

    Events API
  • Send transactional mail

    Mail::to(...)->send(new OrderShippedMailable($order))

    Emails API
  • Strip UTMs from signed URLs

    BentoSignatureExclusion::class middleware

    Middleware

Troubleshooting

Not Authorized

Verify publishable + secret keys and ensure the team member still has access.

Rate Limited

Implement exponential backoff for batch operations and honor Retry-After headers.

Network Errors

Confirm outbound traffic can reach app.bentonow.com and retry transient socket resets.

Payload Exceptions

Double-check Author emails and ensure payloads match the documented schema.

Debugging tips

  1. Enable `logErrors: true` inside `config/bento.php` so request IDs, payload snippets, and failures land in your preferred log channel.
  2. Wrap SDK calls in try/catch blocks or dispatch them to queued jobs with `backoff`/`retryUntil` to gracefully handle rate limits and transient network errors.
  3. Start imports with 200–300 subscribers or events per batch, confirm payloads validate cleanly, then scale toward the 1,000 record API limit.

FAQ

Can I use this SDK in a frontend environment?

No. The Laravel SDK requires your publishable key, secret key, and Site UUID, so it must stay on the server. Use the JavaScript SDK or proxy requests through your own authenticated API for browser tracking.

How do I handle rate limiting?

Queue SDK work or wrap it with Laravel’s retry helpers. Use exponential backoff, respect `Retry-After`, and log headers so you can spot hot paths quickly.

rate_limiting

php
use Illuminate\Support\Facades\Retry;

Retry::times(3)->exponentialBackoff(100)->try(function () {
  // Your Bento API calls here
});
What's the maximum batch size for importing subscribers or events?

The API accepts up to 1,000 subscribers or events per call. For day-to-day imports stick to 200–300 records, and use queued jobs for massive backfills.

max_batch_size

php
$subscribers->chunk(1000)->each(function ($chunk) {
  ProcessBentoImport::dispatch($chunk);
});
How do I track anonymous users?

An email address is required today. Capture emails early (gated content, quizzes, post-checkout) so events and subscriber updates can run server-side.

Which PHP versions are supported?

Target PHP 8.1 or higher on Laravel 9+. The installer relies on Laravel Prompts, so Laravel 10+ delivers the best experience.

How can I contribute to the SDK?

Open issues or pull requests at github.com/bentonow/bento-laravel-sdk and join the community Discord for implementation help.

Can I use the SDK with Laravel Vapor or other serverless environments?

Yes. Initialize the client once per container, keep payloads compact, and offload long-running imports to queues so cold starts stay predictable.

Contribute or debug further

The Laravel SDK is open source. Report bugs, request features, or open pull requests at https://github.com/bentonow/bento-laravel-sdk. Keep your local tooling on Laravel 9+ / PHP 8.1+ to match production builds.

Need the original Markdown? Open raw file