sdk-laravel maintained by emailfunnelai
Email Funnel AI — Laravel
Official Laravel package for the Email Funnel AI
integration API. Ships a self-contained client with a service provider, a
facade, and publishable config — credentials come from your .env, and the
client is auto-wired into the container.
- Laravel 10, 11, 12
- Auto-discovered provider +
EmailFunnelAifacade - Fully standalone — the only dependency is
illuminate/support
Installation
composer require emailfunnelai/laravel
Publish the config (optional):
php artisan vendor:publish --tag=emailfunnelai-config
Set your credentials in .env:
EMAILFUNNELAI_PROJECT_KEY=pk_your_project_key
EMAILFUNNELAI_PROJECT_SECRET=sk_your_secret_key
# Optional — defaults to https://app.emailfunnel.ai.
# Set only for staging, a self-hosted instance, or local development.
# EMAILFUNNELAI_BASE_URL=http://127.0.0.1:8000
Usage
Via the facade:
use EmailFunnelAi\Laravel\Facades\EmailFunnelAi;
EmailFunnelAi::validate();
$list = EmailFunnelAi::lists()->create('Newsletter signups');
EmailFunnelAi::contacts()
->sync(['email' => 'jane@example.com', 'source_type' => 'app'])
->toList($list['id']);
EmailFunnelAi::analytics()->dashboard();
Or resolve the client from the container / inject it:
use EmailFunnelAi\Laravel\Client;
public function __construct(private Client $emailFunnel) {}
// ...
$this->emailFunnel->bindings()->all();
The full resource surface is contacts, lists, bindings, fieldMappings,
autoTagging, analytics, and sso, plus a top-level validate.
Responses & errors
Successful calls return the unwrapped data payload as an array. Any error
response raises a typed exception:
use EmailFunnelAi\Laravel\Exceptions\ApiException;
use EmailFunnelAi\Laravel\Exceptions\TransportException;
try {
EmailFunnelAi::contacts()->sync(['email' => 'invalid'])->toList($listId);
} catch (ApiException $e) {
$e->status; // 422
$e->errorType; // "validation_error"
$e->messages; // ['contact.email' => ['The email field is required.']]
$e->retryAfter; // set on 429 rate limits
} catch (TransportException $e) {
// network / timeout failure (no HTTP response)
}
Branch on errorType for specific conditions — e.g. a contact list that has been
deactivated rejects new members with a 409 list_inactive:
try {
EmailFunnelAi::contacts()->sync($contact)->toList($listId);
} catch (ApiException $e) {
if ($e->errorType === 'list_inactive') {
// The target list is inactive — reactivate it or pick another list.
}
}
Common errorType values
errorType |
Status | Meaning |
|---|---|---|
invalid_credentials |
401 | Missing/invalid project key or secret |
inactive_project |
403 | The connected project is inactive |
validation_error |
422 | Request body failed validation (messages set) |
invalid_email / suppressed |
422 | Email is undeliverable or suppressed |
list_inactive |
409 | Target contact list is inactive and rejects new members |
rate_limit_exceeded |
429 | 1000 req/hour cap hit (retryAfter set) |
sync_failed |
500 | Unexpected sync failure |
Transport & testing
By default the package sends requests through Laravel's HTTP client, so
Http::fake(), retries, logging, and any global middleware apply to SDK calls:
use Illuminate\Support\Facades\Http;
use EmailFunnelAi\Laravel\Facades\EmailFunnelAi;
Http::fake([
'*/api/v1/saas/contacts' => Http::response(['data' => ['contact_id' => 7]], 201),
]);
EmailFunnelAi::contacts()->sync(['email' => 'jane@example.com', 'source_type' => 'app'])->toList($listId);
Http::assertSent(fn ($request) => $request->url() === 'https://app.emailfunnel.ai/api/v1/saas/contacts');
Need a different transport (e.g. a zero-dependency cURL client, a PSR-18 client,
or custom logging)? Bind your own EmailFunnelAi\Laravel\Http\HttpClient:
use EmailFunnelAi\Laravel\Http\CurlHttpClient;
use EmailFunnelAi\Laravel\Http\HttpClient;
$this->app->instance(HttpClient::class, new CurlHttpClient());
Testing
composer install
composer test
Tests use Orchestra Testbench with a recording transport — no network — and a coverage test asserts the facade exposes every documented endpoint.
License
MIT © Email Funnel AI