PHPackages                             letmesendemail/letmesendemail-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. letmesendemail/letmesendemail-php

ActiveLibrary[API Development](/categories/api)

letmesendemail/letmesendemail-php
=================================

letmesend.email PHP SDK.

v0.2.1(3w ago)01591MITPHPPHP ^8.1CI passing

Since Jul 9Pushed 3w agoCompare

[ Source](https://github.com/letmesendemail/letmesendemail-php)[ Packagist](https://packagist.org/packages/letmesendemail/letmesendemail-php)[ Docs](https://letmesend.email/)[ RSS](/packages/letmesendemail-letmesendemail-php/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (10)Dependencies (25)Versions (17)Used By (1)

letmesend.email PHP SDK
=======================

[](#letmesendemail-php-sdk)

[![Tests](https://camo.githubusercontent.com/2a468779872de5accf6db843dd494449a157997ba75a356231d87b203e80d2ee/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6c65746d6573656e64656d61696c2f6c65746d6573656e64656d61696c2d7068702f746573742d7068702e796d6c3f6c6162656c3d7465737473267374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d303030303030)](https://github.com/letmesendemail/letmesendemail-php/actions/workflows/test-php.yml)[![Packagist Downloads](https://camo.githubusercontent.com/1e1fe10d99d028da7da295663f4685b939fa34883d769d36cda90bedf4e381e1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6c65746d6573656e64656d61696c2f6c65746d6573656e64656d61696c2d7068703f7374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d303030303030)](https://packagist.org/packages/letmesendemail/letmesendemail-php)[![Packagist Version](https://camo.githubusercontent.com/fc30643fa2dd467ce8ccbec8f25010104dbac1a2a14f9818408d86cca474a311/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6c65746d6573656e64656d61696c2f6c65746d6573656e64656d61696c2d7068703f7374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d303030303030)](https://packagist.org/packages/letmesendemail/letmesendemail-php)[![License](https://camo.githubusercontent.com/013dde0543197b15bba9a799317f852e50be045bf6eeeb4c48807debc962846e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6c65746d6573656e64656d61696c2f6c65746d6573656e64656d61696c2d7068703f636f6c6f723d396366267374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d3030303030302663616368653d7631)](LICENSE.md)

The official PHP SDK for the [letmesend.email](https://letmesend.email/) API.

Full Documentation
------------------

[](#full-documentation)

See the comprehensive [user manual](docs/docs.md) for complete documentation of every resource, configuration option, retry behavior, error handling, webhook verification, and detailed examples.

Installation
------------

[](#installation)

```
composer require letmesendemail/letmesendemail-php
```

Quick Start
-----------

[](#quick-start)

```
use LetMeSendEmail\LetMeSendEmail;

$apiKey = getenv('LETMESENDEMAIL_API_KEY');
if ($apiKey === false || $apiKey === '') {
    throw new \RuntimeException('LETMESENDEMAIL_API_KEY is not set.');
}

$client = new LetMeSendEmail(apiKey: $apiKey);

$email = $client->emails()->send(
    from: 'Acme ',
    to: ['person@example.com'],
    subject: 'Welcome',
    html: 'Hello from letmesend.email',
);

echo $email->getId(); // "01kvv5a6xk9qd6y2egeae8w76e"
```

Configuration
-------------

[](#configuration)

```
use LetMeSendEmail\LetMeSendEmail;
use LetMeSendEmail\Configuration;

// Simple setup with just an API key:
$client = new LetMeSendEmail(apiKey: 'lms_live_...');

// With custom configuration:
$config = new Configuration(
    apiKey: 'lms_live_...',
    baseUrl: 'https://letmesend.email/api/v1',  // default
    timeout: 60,                                  // default: 30 seconds
);

$client = new LetMeSendEmail(configuration: $config);
```

### Options

[](#options)

OptionDefaultDescription`apiKey`—Your letmesend.email API key`baseUrl``https://letmesend.email/api/v1`API base URL override`timeout``30`Request timeout in seconds`retries``0`Maximum number of retry attemptsAPI Coverage
------------

[](#api-coverage)

This SDK is built from the API fixtures in `data/api-data`.

### Implemented

[](#implemented)

ResourceFixture operationsSDK usageEmails`send`, `send-with-template`, `verify`, `list`, `show``$client->emails()`Domains`list`, `show`, `verify``$client->domains()`Contacts`store`, `list`, `show`, `update`, `delete``$client->contacts()`Contact Categories`store`, `list`, `show`, `update`, `delete``$client->contactCategories()`Email Topics`store`, `list`, `show`, `update`, `delete``$client->emailTopics()`Error responses`daily-quote-exceed`, `monthly-quote-exceed`, `domain-not-found`, `domain-unverified`, `email-size-exceeded`Typed exceptionsWebhooksSignature verification standard`WebhookSignature::verify(...)`Emails
------

[](#emails)

### Send an email

[](#send-an-email)

```
$email = $client->emails()->send(
    from: 'Acme ',
    to: ['person@example.com', 'Jane '],
    subject: 'Welcome to letmesend.email',
    html: 'Welcome!Thanks for signing up.',
    text: 'Welcome! Thanks for signing up.',
    type: 'transactional', // or 'broadcast'
    eventName: 'user.created',
    replyTo: ['support@acme.com'],
    cc: ['manager@acme.com'],
    bcc: ['archive@acme.com'],
    headers: ['X-Custom-Header' => 'value'],
    attachments: [
        [
            'name' => 'report.pdf',
            'mime' => 'application/pdf',
            'path' => 'https://storage.example.com/report.pdf',
        ],
    ],
);

echo $email->getId();         // "01kvv5a6xk9qd6y2egeae8w76e"
echo $email->getStatus();     // "pending_scan" or "accepted"
print_r($email->getEmails()); // list of all recipient addresses
```

### Attachments

[](#attachments)

You can provide attachments as raw arrays or as `Attachment` objects:

```
use LetMeSendEmail\Requests\Attachment;

// From a URL (external file)
$attachment = Attachment::fromPath(
    name: 'report.pdf',
    path: 'https://example.com/report.pdf',
    contentId: 'optional-cid',
    contentDisposition: 'attachment', // or 'inline'
);

// From base64 content
$attachment = Attachment::fromContent(
    name: 'data.txt',
    content: base64_encode('Hello World'),
    contentId: 'optional-cid',
    contentDisposition: 'inline',
);

$email = $client->emails()->send(
    from: 'Acme ',
    to: ['person@example.com'],
    subject: 'With attachment',
    html: 'See attached',
    attachments: [$attachment],
);
```

### Send with a template

[](#send-with-a-template)

```
// Raw arrays:
$email = $client->emails()->sendWithTemplate(
    from: 'Acme ',
    to: ['person@example.com'],
    templateId: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
    subject: 'Your order confirmation',
    templateVariables: [
        ['key' => 'USER_NAME', 'type' => 'string', 'value' => 'John'],
        ['key' => 'ORDER_NUMBER', 'type' => 'number', 'value' => 12345],
    ],
);
```

Or use `TemplateVariable` objects:

```
use LetMeSendEmail\Requests\TemplateVariable;

$email = $client->emails()->sendWithTemplate(
    from: 'Acme ',
    to: ['person@example.com'],
    templateId: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
    templateVariables: [
        new TemplateVariable('USER_NAME', 'string', 'John'),
        new TemplateVariable('ORDER_NUMBER', 'number', 12345),
    ],
);
```

### Idempotency

[](#idempotency)

Pass an `idempotencyKey` to prevent duplicate sends on retry:

```
$email = $client->emails()->send(
    from: 'Acme ',
    to: ['person@example.com'],
    subject: 'Your invoice',
    html: 'Invoice attached',
    idempotencyKey: 'my-unique-key-abc123',
);

if ($email->isDuplicate()) {
    // This send was a duplicate — the original send was not re-attempted.
}
```

When the API returns a duplicate response (same `idempotencyKey` used within the expiry window), the response includes `"duplicate": true` and the `isDuplicate()` method returns `true`.

### Verify an email address

[](#verify-an-email-address)

```
$result = $client->emails()->verify('person@example.com');

echo $result->getStatus();     // "valid", "invalid", or "risky"
echo $result->getScore();      // 0-100
echo $result->hasMailbox();    // true
echo $result->isDisposable();  // false
echo $result->hasValidSyntax(); // true
```

### List emails

[](#list-emails)

```
// First page
$list = $client->emails()->list(perPage: 20);

foreach ($list->items() as $email) {
    echo $email->getId() . ' - ' . ($email->getSubject() ?? '(no subject)') . PHP_EOL;
}

// Pagination info
$pagination = $list->pagination();
echo $pagination->hasMore(); // true
echo $pagination->getTotal(); // 100

// Next page using the last item's ID
if ($list->pagination()->hasMore() && count($list->items()) > 0) {
    $items = $list->items();
    $lastId = $items[count($items) - 1]->getId();
    $nextPage = $client->emails()->list(perPage: 20, after: $lastId);
}

// Previous page using the first item's ID (from a page other than the first)
if (count($list->items()) > 0) {
    $items = $list->items();
    $firstId = $items[0]->getId();
    $prevPage = $client->emails()->list(perPage: 20, before: $firstId);
}
```

### Get a single email

[](#get-a-single-email)

```
$email = $client->emails()->get('01kvv5dv472evp42a60sy4p7zx');

echo $email->getStatus();           // "sent", "queued", etc.
echo $email->getSubject();          // email subject
echo $email->getRecipientsCount();  // 1
echo $email->getAttachmentsCount(); // 0

// Typed recipient objects
foreach ($email->getRecipients() as $recipient) {
    echo $recipient->getEmailAddress(); // "koelpin.burdette@example.org"
    echo $recipient->getStatus();       // "queued", "delivered", "bounced"
    echo $recipient->getOpenCount();    // opens count
    echo $recipient->getClickCount();   // clicks count
}

// Typed attachment objects
foreach ($email->getAttachments() as $attachment) {
    echo $attachment->getName();          // "I9wJnL1QeUOKnsgE.png"
    echo $attachment->getMime();          // "image/png"
    echo $attachment->getSize();          // 16174079
    echo $attachment->getDownloadUrl();   // signed download URL
}
```

Domains
-------

[](#domains)

### List domains

[](#list-domains)

```
$list = $client->domains()->list(perPage: 20);

foreach ($list->items() as $domain) {
    echo $domain->getId() . ' - ' . $domain->getDomainName() . ' (' . $domain->getStatus() . ')' . PHP_EOL;
}

$pagination = $list->pagination();
echo $pagination->hasMore(); // true/false
```

### Get a domain

[](#get-a-domain)

```
$domain = $client->domains()->get('01kvv5th65xzkwxe5avmdqbn4a');

echo $domain->getDomainName(); // "mpxlubowitz.com"
echo $domain->getStatus();     // "verified" or "pending"
```

### Verify a domain

[](#verify-a-domain)

```
$result = $client->domains()->verify('mpxlubowitz.com');

echo $result->getStatus(); // "verified"
```

Contacts
--------

[](#contacts)

### Create a contact

[](#create-a-contact)

```
$contact = $client->contacts()->create(
    email: 'john@example.com',
    firstName: 'John',
    lastName: 'Doe',
    phone: '11231231234',
    categories: ['01kvtsch6f6e7hz543mjyjnqsp'],
);

echo $contact->getId(); // "01kvtsch6t80qpx3ncfea2r5a3"
echo $contact->getFirstName(); // "John"
```

### List contacts

[](#list-contacts)

```
$list = $client->contacts()->list(perPage: 10);

foreach ($list->items() as $contact) {
    echo $contact->getEmail() . ' - ' . $contact->getFirstName() . PHP_EOL;
}
```

### Get a contact

[](#get-a-contact)

```
$contact = $client->contacts()->get('01kvtsch80sxnzw8cggwhh22x2');

echo $contact->getEmail();     // "mayer.elmira@example.com"
echo $contact->getFirstName(); // "Jalen"
```

### Update a contact

[](#update-a-contact)

```
$result = $client->contacts()->update(
    id: '01kvtsch98rxyxwaxwx2fbpsbp',
    firstName: 'John',
    lastName: 'Doe',
    syncCategories: false,
);

echo $result->getId(); // "01kvtsch98rxyxwaxwx2fbpsbp"
```

The `update()` method returns a `ContactUpdateResponse` which contains only the contact ID.

### Delete a contact

[](#delete-a-contact)

```
$result = $client->contacts()->delete('01kvtscham9bjdwftnxxa8at1k');

echo $result->getStatus(); // "success"
```

Contact Categories
------------------

[](#contact-categories)

### Create a category

[](#create-a-category)

```
$category = $client->contactCategories()->create(name: 'New Name');

echo $category->getId();   // "01kvtkm3x5tpyhyw7xcnqf32rj"
echo $category->getName(); // "New Name"
echo $category->getSlug(); // "new-name"
```

### List categories

[](#list-categories)

```
$list = $client->contactCategories()->list(perPage: 20);

foreach ($list->items() as $category) {
    echo $category->getName() . ' (' . $category->getSlug() . ')' . PHP_EOL;
}
```

### Get a category

[](#get-a-category)

```
$category = $client->contactCategories()->get('01kvtr2b9ztdvggdbrcjmm45nj');

echo $category->getName(); // "Category Name"
```

### Update a category

[](#update-a-category)

```
$category = $client->contactCategories()->update(
    id: '01kvtkq34gc5zqbxpyw90q4sk6',
    name: 'New Name',
    slug: 'new-name',
);

echo $category->getSlug(); // "new-name"
```

### Delete a category

[](#delete-a-category)

```
$result = $client->contactCategories()->delete('01kvtmr3evcs2brxp2vcztd102');

echo $result->getStatus(); // "success"
```

Email Topics
------------

[](#email-topics)

### Create a topic

[](#create-a-topic)

```
$topic = $client->emailTopics()->create(
    name: 'Product Updates',
    slug: 'product-updates',
    description: 'Emails for product updates',
    autoSubscribe: true,
    public: true,
    domainId: '01kvtsgfavkx5609jd3j2t6jr1', // optional
);

echo $topic->getId();   // "01kvtsgfb2rp5g1y3xdsrnk6n4"
echo $topic->getName(); // "Product Updates"
```

### List topics

[](#list-topics)

```
$list = $client->emailTopics()->list(perPage: 20);

foreach ($list->items() as $topic) {
    echo $topic->getName() . ' - ' . ($topic->getDescription() ?? '(no description)') . PHP_EOL;
}
```

### Get a topic

[](#get-a-topic)

```
$topic = $client->emailTopics()->get('01kvtsgf9e96nnj98nxsac751r');

echo $topic->getName(); // "Billing Notifications"
echo $topic->isAutoSubscribe(); // false
```

### Update a topic

[](#update-a-topic)

```
$topic = $client->emailTopics()->update(
    id: '01kvtsgfcbte0npnqkj8kep642',
    name: 'New Name',
    description: 'Updated description',
    public: true,
);

echo $topic->getName(); // "New Name"
```

### Delete a topic

[](#delete-a-topic)

```
$result = $client->emailTopics()->delete('01kvtsgfdq4xw54vcvqw0ae68n');

echo $result->getStatus();  // "success"
echo $result->getMessage(); // "Email category deleted"
```

Error Handling
--------------

[](#error-handling)

All API errors throw exceptions that extend `LetMeSendEmail\Exceptions\ApiException`:

HTTP StatusExceptionDescription400, 413, 422`ValidationError`Request validation failed401`AuthenticationError`Invalid or missing API key403`AuthorizationError`Insufficient permissions404`NotFoundError`Resource not found409`ConflictError`Resource conflict429`RateLimitError`Rate limit exceeded500+`ApiError`Server error—`NetworkError`Connection failed—`TimeoutError`Request timed out—`WebhookVerificationException`Webhook verification failed—`WebhookSigningException`Webhook signing configuration error```
use LetMeSendEmail\Exceptions\ValidationError;
use LetMeSendEmail\Exceptions\AuthenticationError;
use LetMeSendEmail\Exceptions\RateLimitError;
use LetMeSendEmail\Exceptions\ApiException;

try {
    $client->emails()->send(/* ... */);
} catch (ValidationError $e) {
    echo $e->getMessage();
    print_r($e->getValidationErrors()); // field-level errors
} catch (AuthenticationError $e) {
    echo 'Check your API key.';
} catch (RateLimitError $e) {
    echo 'Retry after ' . $e->getRetryAfter() . ' seconds.';
} catch (ApiException $e) {
    echo 'HTTP ' . $e->getHttpStatus() . ': ' . $e->getMessage();
}
```

Error exceptions provide:

- `getMessage()` — human-readable error description.
- `getHttpStatus()` — HTTP status code.
- `getApiCode()` — API error code (e.g., `domain_not_found`, `daily_quota_exceeded`).
- `getValidationErrors()` — field-level validation errors, if any.
- `getHeaders()` — response headers.
- `getRequestId()` — request ID for debugging, if present in headers.
- `getRawBody()` — raw response body.

Pagination
----------

[](#pagination)

List endpoints return a response with a `PaginationInfo` object:

```
$list = $client->emails()->list(perPage: 10);

// Items on this page
$emails = $list->items();

// Pagination metadata
$pag = $list->pagination();
$pag->hasMore();   // bool
$pag->getTotal();  // int
$pag->getPerPage(); // int
$pag->getFetched(); // int
```

Use cursor-based pagination:

```
// Next page: use the last item's ID
if ($list->pagination()->hasMore() && count($list->items()) > 0) {
    $items = $list->items();
    $nextList = $client->emails()->list(after: $items[count($items) - 1]->getId());
}

// Previous page: use the first item's ID (from a page other than the first)
if (count($list->items()) > 0) {
    $items = $list->items();
    $prevList = $client->emails()->list(before: $items[0]->getId());
}
```

Retries
-------

[](#retries)

The SDK can automatically retry idempotent requests on transient failures:

```
$config = new Configuration(
    apiKey: 'lms_live_...',
    retries: 3,
);

$client = new LetMeSendEmail(configuration: $config);
```

### When retries happen

[](#when-retries-happen)

- **GET, HEAD, OPTIONS, DELETE** requests are always retryable.
- **POST, PUT, PATCH** requests are retryable only when an `Idempotency-Key` header is present.

### What is retried

[](#what-is-retried)

- `NetworkError` and `TimeoutError` — connection or timeout failures.
- HTTP 408, 429, 500, 502, 503, 504 — retryable server / rate-limit errors.

### Backoff strategy

[](#backoff-strategy)

- **Rate-limit (429):** Uses the `Retry-After` header (delta-seconds or HTTP-date), capped at 300 seconds.
- **Other retryable errors:** Bounded exponential backoff with ±25% jitter starting at 100ms base delay. Each delay is capped at 300 seconds. Large attempt numbers are clamped to prevent overflow.

All delays are testable via the `SleeperInterface` (`LetMeSendEmail\Http\SleeperInterface`).

Webhooks
--------

[](#webhooks)

Webhook signature verification is built in:

```
use LetMeSendEmail\Support\WebhookSignature;
use LetMeSendEmail\Exceptions\WebhookVerificationException;
use LetMeSendEmail\Exceptions\WebhookSigningException;

$payload = file_get_contents('php://input');
if ($payload === false) {
    http_response_code(400);
    echo 'Failed to read request body.';
    exit;
}

$headers = getallheaders();
if (!is_array($headers)) {
    $headers = [];
}

$secret = getenv('LETMESENDEMAIL_WEBHOOK_SECRET');
if ($secret === false || $secret === '') {
    http_response_code(500);
    echo 'Webhook secret is not configured.';
    exit;
}

try {
    $event = WebhookSignature::verify(
        payload: $payload,
        headers: $headers,
        secret: $secret,
        tolerance: 300,
    );

    // $event contains the verified payload as an associative array
} catch (WebhookVerificationException $e) {
    http_response_code(400);
    echo 'Webhook verification failed: ' . $e->getMessage();
} catch (WebhookSigningException $e) {
    http_response_code(500);
    echo 'Webhook configuration error: ' . $e->getMessage();
}
```

The verifier reads four headers (`webhook-id`, `webhook-log-id`, `webhook-timestamp`, `webhook-signature`), validates the timestamp against a configurable tolerance (default 300 seconds), and checks the HMAC-SHA256 signature against the decoded secret.

The signing secret may be prefixed with `whsec_`. The prefix is stripped automatically before decoding.

The `webhook-signature` header supports multiple space-separated versioned signatures. Only `v1` signatures are accepted; unknown versions are ignored.

Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
```

Version Support
---------------

[](#version-support)

PHP VersionSupported8.1Yes8.2Yes8.3Yes8.4Yes8.5YesChangelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance94

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Total

16

Last Release

26d ago

PHP version history (3 changes)1.0.3PHP ^8.2

1.0.8PHP ^8.1

1.0.11PHP ^8.2|^8.3|^8.4|^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/b12b084b3ba23d69fd03407818c2918d94f31aca4a3f5b5f9be524cf9944a182?d=identicon)[letmesendemail](/maintainers/letmesendemail)

---

Top Contributors

[![gurindersingh](https://avatars.githubusercontent.com/u/6223752?v=4)](https://github.com/gurindersingh "gurindersingh (9 commits)")

---

Tags

phpapiclientsdkletmesendletmesend-email

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/letmesendemail-letmesendemail-php/health.svg)

```
[![Health](https://phpackages.com/badges/letmesendemail-letmesendemail-php/health.svg)](https://phpackages.com/packages/letmesendemail-letmesendemail-php)
```

###  Alternatives

[openai-php/laravel

OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with the Open AI API

3.7k10.2M94](/packages/openai-php-laravel)[resend/resend-php

Resend PHP library.

608.3M51](/packages/resend-resend-php)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

293.1k](/packages/eslazarev-wildberries-sdk)[resend/resend-laravel

Resend for Laravel

1223.2M10](/packages/resend-resend-laravel)[checkout/checkout-sdk-php

Checkout.com SDK for PHP

563.6M16](/packages/checkout-checkout-sdk-php)[mozex/anthropic-laravel

Laravel integration for the Anthropic API: facade, config publishing, install command, testing fakes, messages, streaming, tool use, thinking, and batches.

76364.8k1](/packages/mozex-anthropic-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
