Skip to content

SDK Choice & Structure Review ​

A health check of telegram-bot-essentials/essence's structure, plus an evaluation of whether irazasyed/telegram-bot-sdk was the right foundation for the vshop Telegram bot platform (essence + billing/wallet/affiliates/announcements/settings/gateway-card/gateway-zibal on top).

Point-in-time review — two findings are now fixed

This page is a snapshot from before v0.12.0. The two concrete issues in Bus / handler architecture below — encodeCallback() truncating silently, and bus registration re-running on every request — were both fixed in v0.12.0: encodeCallback() now throws CallbackDataTooLong, and handlers register once per worker boot in TelegramBotServiceProvider (see Architecture → Handler Registration). Recommendation #3 (an abstraction seam over Telegram\Bot\*) is still open.

Verdict, up front ​

irazasyed/telegram-bot-sdk was a reasonable choice and remains defensible today. It's the most Laravel-native option among the PHP libraries surveyed (ServiceProvider, multi-bot BotsManager, config-driven), it's actively maintained, and it's permissive enough to fork — which the project already did (Elyar0/telegram-bot-sdk@feature/local-file-path-download) to support local Bot API server file downloads.

It is deliberately thin — a raw object/HTTP wrapper with no conversation, routing, or multi-tenancy concepts. That's exactly why tbe-essence had to be built: a bus/routing/state framework layered on top. The real risk isn't "wrong SDK", it's that 34 of ~90 src/ files in essence import Telegram\Bot\* directly, with no adapter boundary — see SDK Coupling below.

Structural Health Check ​

SDK coupling vs abstraction ​

Api, Update, BaseObject, Command, Keyboard, InputFile, TelegramSDKException etc. are used directly across Http/Controllers, Models, Services, every CallbackQueries/StateAnswers handler, and TelegramResponse. There is no TelegramApiContract-style interface between tbe-essence and the SDK.

  • This coupling is deep, but to a well-aligned dependency — irazasyed's multi-bot, Laravel-container-friendly design is what tbe-essence's own architecture mirrors.
  • It has already been forked once (local-file-path-download), which proves the SDK is mutable when needed — but a fork must be manually kept in sync with upstream; there's no auto-update path, so drift risk grows over time.
  • Cost of this coupling only becomes real if: the SDK goes unmaintained, Telegram's API grows faster than a single-maintainer fork can track, or a clearly better-fit alternative emerges. See Migration Cost.

Bus / handler architecture ​

The Commands → ReplyKeys → StateAnswers priority chain, plus separate CallbackQuery and InlineQuery buses (see Architecture), is a solid, already-proven-extensible design — every companion package (billing, wallet, affiliates, announcements, settings, gateway-card, gateway-zibal) hooks in via addCallbackQuery() / addStateAnswer() / addReplyKey() from its own service provider, with no changes needed to essence itself.

Two concrete issues found:

  1. encodeCallback() silently truncates on overflow. In src/helpers.php:

    php
    if (strlen($result) > 64) {
        return 'LONG_CALLBACK_DATA'; // TODO: handle this case
    }

    When a callback payload exceeds Telegram's 64-byte callback_data limit, this ships a button with the literal string LONG_CALLBACK_DATA as its callback data instead of failing loudly. The docs already describe the correct workaround (stash payload in StateData, pass only the ID) — but nothing enforces it. This is a live footgun for any new Feature that forgets it.

  2. Bus registration is rebuilt on every webhook request, inside an Octane-persistent singleton.TelegramWebhookController::initializeOptions() rescans app/Telegram/** directories and re-registers every Command/CallbackQuery/StateAnswer/ReplyKey class on every invocation. The buses (ReplyKeyBus, StateAnswerBus, CallbackQueryBus, InlineQueryBus) are registered as singleton() in TelegramBotServiceProvider, meaning under Octane+RoadRunner (this project's deployment) the same bus instance survives across requests on a worker, and gets redundantly re-populated every single time. Notably, Webhook::class was deliberately made scoped() instead, with an explicit comment reasoning about Octane worker persistence — proof the team already understands the issue class, just didn't apply it here. Not a correctness bug (arrays are keyed by type/text, so re-registration overwrites idempotently) but it's pure repeated filesystem-scan + reflection-instantiation tax, in exactly the deployment mode meant to eliminate that kind of tax.

Multi-tenancy ​

Stancl Tenancy usage is idiomatic: Bot extends BaseTenant, BotUser uses BelongsToTenant, tenancy middleware is given top priority, and the webhook route resolves tenants by path. No issues found.

MessageMeta / StateData ​

Clean separation of concerns — MessageMeta tracks which Telegram message to lock/revert/update, StateData holds arbitrary JSON payload for multi-step forms, decoupled specifically so callback_data can stay within the 64-byte limit by referencing a StateData ID instead of carrying the payload inline. Well-designed, matches the project's own State & StateData docs.

Recommendations ​

#FindingRecommendation
1encodeCallback() returns 'LONG_CALLBACK_DATA' silently on overflowThrow a dedicated exception (fail loud in dev/staging), or auto-route oversized payloads through StateData transparently instead of leaving it a manual convention
2Bus registration rescans + re-registers every request under Octane singletonsLoad once per worker boot (guard with a boolean flag on the singleton, or move registration into boot() triggered once) instead of per-request File::allFiles() + reflection
3No abstraction boundary over Telegram\Bot\* (34 files)Introduce a thin interface for the highest-traffic call sites (wHook()->api() surface, Update/Keyboard construction) incrementally — not urgent today, but do it before Mini App work adds new API surface, so that addition has a seam to land on rather than more direct SDK coupling

SDK Comparison ​

Evaluated against all 20 PHP libraries listed on core.telegram.org/bots/samples.

LibraryStarsMaintained?Laravel fitFramework vs raw wrapperMini-App/newer-APIVerdict
irazasyed/telegram-bot-sdk (current)3306ActiveBuilt-inRaw wrapper + light structure (Commands, Objects, Keyboard)NoBaseline — thin, stable, unopinionated; exactly why tbe-essence had to build its own bus/routing/state layer on top
Nutgram740ActiveVia nutgram/laravel pkgFramework: conversations, middleware, groups/scopesNot framework-nativeStrongest architectural alternative — has out-of-the-box what tbe-essence built by hand
Telegraph817ActiveNative Laravel packageFluent wrapper, no state/conversation systemNoneNicer DX for one-off sends, no answer to tbe-essence's bus needs
LaraGram129ActiveStandalone Laravel-like framework (not atop Laravel)Full framework: Step Manager, Conversations, multi-bot, MTProtoYes — TMA (Mini Apps) is coreBest Mini-App story on the list, but replaces Laravel itself — non-starter for an existing Laravel app
laragram (Mirmuxsin)42Semi-activeLaravel packageThin wrapperUnlikelyToo low-signal for production swap
TeleBot306ActiveLaravel adapter pkgOOP + functional handlers, BotManager for multi-botNot documentedMulti-bot support notable — 2nd-tier candidate, less battle-tested
php-telegram-bot/core4024Stalled ("on hold")NoneRaw wrapper + plugin/conversation system, single-botNoMost stars but explicitly paused — disqualified
unreal4u/telegram-api785ArchivedNoneAsync raw wrapperNoDead — disqualified
NovaGram187Stale (2023)NoneOOP wrapper, built-in ConversationsNoUnmaintained, no Laravel — disqualified
klev-o/telegram-bot-api46StaleNoneSimple OOP wrapperNoToo small
vjik/telegram-bot-api128ActiveNone (Yii ecosystem)Raw wrapperNoNot Laravel-oriented
TelegramBot/Api1189ActiveNoneRaw wrapperNoNo framework features, no Laravel
tgWebValid68ActiveN/ANot a bot SDK — Mini App/Login Widget validator onlyYes (sole purpose)Complementary tool, not a replacement SDK
TuriBot122ActiveNoneSimple wrapperNoLow signal
kuvardin/TelegramBotsApi17ActiveNoneRaw wrapperNoToo small
telegram-bot-php/core61StaleNoneSimple wrapperNoLow signal
TgBotLib5StaleNone (ncc ecosystem)Raw wrapperNoToo small/niche
NeleBotX28DeadNoneFramework attemptNoAbandoned
BPT9DeadNoneSimple wrapperNoAbandoned
BotAPI (TelegramSDK)26StaleNoneRaw wrapperNoToo small

Top realistic alternatives ​

  1. Nutgram — the clearest architectural peer to what tbe-essence built manually: native conversation/state-machine system, middleware, scope/group handlers, plus an official Laravel package. Where irazasyed/telegram-bot-sdk gave nothing but raw API objects (forcing the custom bus/StateAnswer/CallbackQuery machinery), Nutgram would have provided much of that scaffolding pre-built — at the cost of migrating the whole webhook/routing model to its middleware paradigm.
  2. TeleBot — smaller and newer, but its BotManager gives first-class multi-bot switching, which maps directly onto vshop's multi-tenant-bot requirement (essence had to build tenant resolution itself via Stancl instead). Worth watching, far less battle-tested (306 stars).
  3. LaraGram — best native Mini Apps story of anything surveyed, but it's a standalone Laravel-style framework, not a package for an existing Laravel app. A "watch" item, not a migration candidate.

Everything else is either a raw wrapper with no framework ambitions (matching or weaker than irazasyed), abandoned/archived (php-telegram-bot/core, unreal4u, NovaGram, NeleBotX, BPT), or too low-signal (<50 stars, stale) to trust for a production SaaS platform.

How tbe-essence Itself Compares ​

Setting the low-level SDK question aside, tbe-essence's own design holds up well against the field surveyed above:

  • A real MVC-ish split. Feature (view — builds TelegramResponse + keyboards) → CallbackQuery/StateAnswer (controller — handles input, orchestrates) → Eloquent Model. Most raw wrappers (php-telegram-bot, unreal4u, TelegramBot/Api, klev-o, TuriBot, ...) impose no structure at all — you get API objects and nothing else. Only Nutgram and LaraGram impose comparable structure, but through their own middleware/conversation DSL rather than a pattern Laravel developers already know.
  • Package/plugin model via service providers. billing, user-wallet, affiliates, announcements, gateway-card, gateway-zibal all hook into the shared buses (addCallbackQuery() / addStateAnswer() / addReplyKey()) from their own boot(), with zero changes to essence core. This is straight Laravel idiom — arguably tighter Laravel integration than Nutgram gets from its bolt-on nutgram/laravel package.
  • Event bus reuses Laravel's own event system (BotCommandHandled, BotCallbackQueryHandled, etc. — see Events & Listeners) instead of inventing a new middleware/hook vocabulary. Lower learning curve for anyone already familiar with Laravel events.
  • Multi-tenancy is the standout differentiator. None of the 20 libraries surveyed —not Nutgram, not TeleBot's BotManager, not LaraGram— natively solve "N independently-configured bots, each with tenant-scoped Eloquent data, in one app." TeleBot switches which bot is active; it doesn't isolate data per bot the way BelongsToTenant + Stancl does here.
  • The 64-byte callback_data constraint is solved deliberately, not discovered by accident. encodeCallback()/encodeAnswerState() plus the StateData overflow path (see State & StateData) handle a sharp, easy-to-miss Telegram limit that a lot of naive bot code gets bitten by.

Honest caveat: a large share of this — bus routing, conversation/state machine, per-flow handler classes — is what Nutgram ships out of the box. tbe-essence effectively rebuilt roughly 70% of Nutgram's feature set by hand, on top of a thinner base SDK. That effort wasn't wasted — it bought Laravel-native idiom and multi-tenancy Nutgram doesn't have — but it is real, in-house-carried maintenance burden that an upstream framework would otherwise absorb.

Migration Cost Estimate ​

Not currently justified — no forcing function (no maintenance abandonment upstream, no urgent bug) — but for the record:

  • 34 files in tbe-essence import Telegram\Bot\* directly; vshop's own app/Telegram/ tree likely adds a comparable count (not separately audited here).
  • Highest-cost surfaces to replace: Webhook::api() (the Api instance itself), Update/ BaseObject used throughout CallbackQuery/StateAnswer/Feature classes to parse incoming payloads, Keyboard/Button for outgoing markup, InputFile for uploads, the Command base class for slash commands, and the custom downloadFile() / local-file-path fork behavior, which would need reimplementing against any new client.
  • Character of the migration: high blast radius, low individual complexity — many mechanical touch points, not deep logic rewrites. Multi-week effort as a big-bang rewrite for a solo maintainer; much cheaper if the interface boundary (Recommendation #3) is built first and the swap happens behind it incrementally.

Future Feature Fit (Mini Apps & beyond) ​

Telegram Mini Apps / WebApp validation mostly lives at the HTTP-endpoint layer — validating the initData HMAC — which is largely independent of whichever Bot API wrapper handles webhooks. This means Mini App support is more "add a validation middleware" than "need a different core SDK" — a library like tgWebValid (validator-only, not a competing bot SDK) can sit alongside irazasyed/telegram-bot-sdk without conflict. Other emerging Bot API surface (Stars payments, Business accounts, etc.) will land as new Telegram\Bot\* methods/objects the same way current ones do — no structural blocker, just more surface area touching the 34 already-coupled files, which reinforces why Recommendation #3 (an abstraction seam) is worth doing proactively rather than reactively.