tourze/wechat-work-group-welcome-template-bundle
A PHP bundle for managing WeChat Work (企业微信) group welcome message templates. It provides services and configuration to create, organize, and reuse onboarding greetings for customer groups, helping teams keep welcome messages consistent and reduce manual setup.
This is a Symfony bundle, not a Laravel-native package, and it targets the WeCom (企业微信 / WeChat Work) group welcome template (入群欢迎语素材) APIs. Treat it as a thin domain wrapper around externalcontact/group_welcome_template/* and externalcontact/send_welcome_msg rather than a full SDK.
Install and inspect before wiring anything up
composer require tourze/wechat-work-group-welcome-template-bundle
Then read, in this order:
src/*Bundle.php — the bundle class; shows what services/entities are registered.src/Entity/ — the Doctrine entities that mirror WeCom template payloads (text / image / link / miniprogram / file).src/Service/ or src/Repository/ — the actual API call sites; this is where the real behaviour lives.tourze/wechat-work-bundle (or equivalent) dependency — this bundle almost certainly delegates HTTP transport, access-token caching, and media_id uploads to it. Most of your configuration lives there, not here.Registration
config/bundles.php, then import the bundle's routing/services as documented in its README/Resources/config.tuck/eloquent-bundle-style shims) or, more pragmatically, instantiate the bundle's service classes directly and inject your own HTTP client. Do not expect Laravel facades, config publishing, or a config/wechat-work.php file.Configuration you actually need: corp ID, the external-contact agent's secret, and a cache pool for the access token. Confirm where the token provider comes from — that's the single most common setup failure.
First use case — the happy path worth validating end-to-end:
media_ids; it does not upload them for you).welcome_code from the event and call send_welcome_msg with that code and your stored template.That last step is the whole point of the package: templates are material, and the welcome_code is a 20-minute-lived ticket to send one.
Store templates locally, send by reference. The WeCom API has no "list templates" call that's useful at runtime — you add/edit/delete templates and get back a template_id. Mirror them in your own table (the bundle ships Doctrine entities; map them to Eloquent models if you're in Laravel) with columns for template_id, owner userid, and a type/scene discriminator. Sending then becomes a local lookup plus one API call.
Two attachment modes, pick one deliberately.
userid; every new group that rep joins gets it automatically. Good for uniform onboarding flows, bad for per-campaign copy.welcome_code (added-contact events) or chat_id (group events). This is what you want for A/B copy or per-campaign variants.Queue the send. welcome_code has a short lifetime (20 seconds from the callback). Do the send in a job dispatched immediately and not behind a backlog of other work; if the queue is cold, send synchronously in the webhook handler instead. Store the code on the job payload and mark it consumed on success — retries after expiry are pointless and should fail fast.
Idempotency key on the template side. Since add returns a new template_id every time, a naive "sync templates on boot" seeder will spam duplicates and hit rate limits. Keep a template_id in your DB, compare a content hash, and only call edit when it changes.
Wrap the bundle service, don't inject it everywhere. Put a single WelcomeTemplateSender application service in front of it:
final class WelcomeTemplateSender
{
public function __construct(
private GroupWelcomeTemplateService $templates, // from the bundle
private HttpClientInterface $media, // for uploads
) {}
public function sendForNewContact(string $welcomeCode, string $ownerUserId): void
{
$template = $this->templates->findDefaultFor($ownerUserId);
$this->templates->send($welcomeCode, $template); // expires in ~20s
}
}
This gives you one seam to swap when the 0.x API moves, and one place to add logging/metrics.
Media pipeline is your responsibility. Images and files need a media_id from the WeCom media upload endpoint; those IDs are temporary (roughly three days). Cache media_id → your asset ID with a TTL, and re-upload on expiry rather than letting sends fail. Mini-program attachments use appid + page, no media upload.
Integration tip — align with your contact-event source. If you're already consuming WeCom callback XML (or a relay's webhooks), the welcome_code arrives on the change_external_contact / add_external_contact event. Keep template storage and event handling in the same module so the code → template → send story is readable in one place.
0.0.1 and 0.0.2, so there is nothing to attribute. Treat 0.0.x as an unstable API: pin an exact version in composer.json, and after any bump diff src/Entity/ and src/Service/ before trusting your tests. Don't assume semver protection.HttpClientInterface, EventDispatcherInterface, and Doctrine EntityManagerInterface. Plan for a small bridge/adapter layer instead of fighting it service-by-service.welcome_code expiry is the #1 production failure. Twenty seconds. Never persist codes for later, never let them sit in a retry queue, and never batch them.userid. If your product implies "multiple defaults," enforce the choice in your own UI/domain layer.40058 invalid parameter errors.42001 access_token expired errors. Ensure a single shared cache pool backs the token provider.40001, 40058, 45009) are precise about which field is wrong; the bundle will likely surface them as a generic exception with the code string, so capture the payload yourself.https://qyapi.weixin.qq.com/cgi-bin/externalcontact/... yourself for the handful of endpoints you actually use, rather than patching the bundle mid-0.x.How can I help you explore Laravel packages today?