定制 letmesendemail/letmesendemail-php 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

letmesendemail/letmesendemail-php

Composer 安装命令:

composer require letmesendemail/letmesendemail-php

包简介

letmesend.email PHP SDK.

README 文档

README

Tests Packagist Downloads Packagist Version License

The official PHP SDK for the letmesend.email API.

Installation

composer require letmesendemail/letmesendemail-php

Quick Start

use LetMeSendEmail\LetMeSendEmail;

$client = new LetMeSendEmail(apiKey: $_ENV['LETMESENDEMAIL_API_KEY']);

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

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

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

Option Default Description
apiKey Your letmesend.email API key
baseUrl https://letmesend.email/api/v1 API base URL override
timeout 30 Request timeout in seconds

API Coverage

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

Implemented

Resource Fixture operations SDK usage
Emails 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 exceptions
Webhooks Signature verification standard WebhookSignature::verify(...)

Emails

Send an email

$email = $client->emails()->send(
    from: 'Acme <hello@acme.com>',
    to: ['person@example.com', 'Jane <jane@example.com>'],
    subject: 'Welcome to letmesend.email',
    html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
    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',
            'download_url' => 'https://...',
        ],
    ],
);

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

Send with a template

$email = $client->emails()->sendWithTemplate(
    from: 'Acme <hello@acme.com>',
    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],
    ],
);

Idempotency

Pass an idempotencyKey to prevent duplicate sends on retry:

$email = $client->emails()->send(
    from: 'Acme <hello@acme.com>',
    to: ['person@example.com'],
    subject: 'Your invoice',
    html: '<p>Invoice attached</p>',
    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

$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

// 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 cursor
$list = $client->emails()->list(perPage: 20, after: 'cursor_from_previous_page');

// Previous page using cursor
$list = $client->emails()->list(perPage: 20, before: 'cursor_from_previous_page');

Get a single email

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

echo $email->getStatus();           // "sent", "queued", etc.
echo $email->getSubject();          // email subject
echo $email->getRecipientsCount();  // 1
print_r($email->getRecipients());   // recipient details
print_r($email->getAttachments());  // attachment details

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

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

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

Verify a domain

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

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

Contacts

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 = $client->contacts()->list(perPage: 10);

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

Get a contact

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

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

Update a contact

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

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

Delete a contact

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

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

Contact Categories

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 = $client->contactCategories()->list(perPage: 20);

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

Get a category

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

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

Update a category

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

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

Delete a category

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

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

Email Topics

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 = $client->emailTopics()->list(perPage: 20);

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

Get a topic

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

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

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

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

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

Error Handling

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

HTTP Status Exception Description
400, 413, 422 ValidationError Request validation failed
401 AuthenticationError Invalid or missing API key
403 AuthorizationError Insufficient permissions
404 NotFoundError Resource not found
409 ConflictError Resource conflict
429 RateLimitError Rate limit exceeded
500+ 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

List endpoints return an EmailListResponse 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:

// After a specific item
$list = $client->emails()->list(after: $cursor);

// Before a specific item
$list = $client->emails()->list(before: $cursor);

Webhooks

Webhook signature verification is built in:

use LetMeSendEmail\Support\WebhookSignature;

$payload = file_get_contents('php://input');
$headers = getallheaders();

try {
    $event = WebhookSignature::verify(
        payload: $payload,
        headers: $headers,
        secret: $_ENV['LETMESENDEMAIL_WEBHOOK_SECRET'],
        tolerance: 300,
    );

    // $event contains the parsed webhook payload
    switch ($event['event'] ?? '') {
        case 'email.delivered':
            // handle delivery
            break;
        case 'email.bounced':
            // handle bounce
            break;
    }
} catch (\LetMeSendEmail\Exceptions\WebhookVerificationException $e) {
    http_response_code(400);
    echo 'Webhook verification failed: ' . $e->getMessage();
} catch (\LetMeSendEmail\Exceptions\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

composer install
vendor/bin/pest

Version Support

PHP Version Supported
8.1 Yes
8.2 Yes
8.3 Yes
8.4 Yes
8.5 Yes

Changelog

See CHANGELOG.md.

统计信息

  • 总下载量: 1
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-09

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固