<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry Rechkin</title>
    <description>The latest articles on DEV Community by Dmitry Rechkin (@dmitryrechkin).</description>
    <link>https://dev.to/dmitryrechkin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1470287%2F9cead2f3-1870-4245-8640-46e20389be3b.png</url>
      <title>DEV Community: Dmitry Rechkin</title>
      <link>https://dev.to/dmitryrechkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmitryrechkin"/>
    <language>en</language>
    <item>
      <title>I Stopped Clicking Around wp-admin and Treated WordPress Media Offload Like Infrastructure</title>
      <dc:creator>Dmitry Rechkin</dc:creator>
      <pubDate>Sun, 08 Mar 2026 01:20:28 +0000</pubDate>
      <link>https://dev.to/dmitryrechkin/i-stopped-clicking-around-wp-admin-and-treated-wordpress-media-offload-like-infrastructure-1kae</link>
      <guid>https://dev.to/dmitryrechkin/i-stopped-clicking-around-wp-admin-and-treated-wordpress-media-offload-like-infrastructure-1kae</guid>
      <description>&lt;h1&gt;
  
  
  I Stopped Clicking Around wp-admin and Treated WordPress Media Offload Like Infrastructure
&lt;/h1&gt;

&lt;p&gt;WordPress media libraries have a habit of turning into infrastructure problems in slow motion. At first it is just uploads. Then it is product galleries, thumbnails, originals, PDFs, course files, and downloads piling up on the same server that is supposed to stay lean.&lt;/p&gt;

&lt;p&gt;That arrangement does not age well. Disks fill up, backups get bloated, restore times get longer, and moving a site between environments becomes more painful than anyone expected. The media library quietly becomes one of the least portable parts of the stack.&lt;/p&gt;

&lt;p&gt;The first fix is straightforward: stop keeping all of that media on the WordPress server. Offloading uploads to object storage such as Cloudflare R2, Amazon S3, or Google Cloud Storage takes that weight off the app layer and makes the stack easier to scale.&lt;/p&gt;

&lt;p&gt;But offloading media creates a second problem. Somebody still has to configure the provider, test the account, migrate existing files, verify URL rewriting, and repeat the setup without drift across environments. That is where a settings page starts to feel pretty flimsy.&lt;/p&gt;

&lt;p&gt;If media offload matters to your stack, it needs a repeatable CLI workflow with &lt;code&gt;wp&lt;/code&gt; and &lt;code&gt;wp cloudsync&lt;/code&gt;, explicit verification steps, and clear boundaries for where AI is allowed to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treat WordPress media offload as infrastructure, not as a page full of one-time clicks.&lt;/li&gt;
&lt;li&gt;Use WP-CLI as the execution layer.&lt;/li&gt;
&lt;li&gt;Use agent skills for repeatability, not for "magic."&lt;/li&gt;
&lt;li&gt;Verify what your installed plugin edition actually supports with &lt;code&gt;wp help cloudsync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Be honest about what you have tested locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The thing that kept bugging me
&lt;/h2&gt;

&lt;p&gt;Most WordPress automation discussions get weirdly abstract, weirdly fast.&lt;/p&gt;

&lt;p&gt;People jump straight to "AI agents managing infrastructure" without first answering a much more boring question: is the workflow itself any good?&lt;/p&gt;

&lt;p&gt;If the setup depends on memory, screenshots, and whoever configured production six months ago, an agent is not going to save you. It will just fail faster and in a more exciting way.&lt;/p&gt;

&lt;p&gt;The useful version is much smaller:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write down the exact CLI steps.&lt;/li&gt;
&lt;li&gt;Decide what is allowed to run.&lt;/li&gt;
&lt;li&gt;Add verification points.&lt;/li&gt;
&lt;li&gt;Let the agent execute the boring parts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. No mystery. No hand waving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why media offload becomes an infrastructure concern
&lt;/h2&gt;

&lt;p&gt;Once uploads stay on the app server, you get the usual headaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;disk usage grows in places you were hoping would stay disposable&lt;/li&gt;
&lt;li&gt;backups get bigger and slower&lt;/li&gt;
&lt;li&gt;multi-environment parity gets sloppy&lt;/li&gt;
&lt;li&gt;stateless deployments stop being truly stateless&lt;/li&gt;
&lt;li&gt;migration work gets riskier than it needs to be&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving media to object storage helps. Doing it repeatably is the real win.&lt;/p&gt;

&lt;p&gt;That is why WP-CLI matters here. A GUI is fine for exploration. It is not a serious operating model for repeated rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI actually fits
&lt;/h2&gt;

&lt;p&gt;A plugin with meaningful CLI coverage gives an agent something it can use safely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;known commands&lt;/li&gt;
&lt;li&gt;known flags&lt;/li&gt;
&lt;li&gt;visible output&lt;/li&gt;
&lt;li&gt;a place to stop and verify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CloudSync Master exposes this through &lt;code&gt;wp cloudsync&lt;/code&gt;, but the exact command set depends on whether you are running the free plugin or Pro. Check &lt;code&gt;wp help cloudsync&lt;/code&gt; on the target site first. Do not write a giant automation layer around commands you have not even confirmed exist.&lt;/p&gt;

&lt;p&gt;If your team uses reusable agent instructions, 1TeamSoftware already publishes them here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/1TeamSoftware/skills/tree/main/1teamsoftware" rel="noopener noreferrer"&gt;1TeamSoftware skills repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/1TeamSoftware/skills/tree/main/1teamsoftware/wp-cloudsync-master" rel="noopener noreferrer"&gt;wp-cloudsync-master skill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/1TeamSoftware/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; 1teamsoftware/wp-cloudsync-master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the agent plugin-specific instructions. It does not replace judgment. It just reduces guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was verified locally
&lt;/h2&gt;

&lt;p&gt;On a LocalWP site, I was able to get WP-CLI working against the correct PHP runtime and MySQL socket, then verify these free CloudSync commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync status
wp cloudsync accounts list
wp cloudsync &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those worked locally.&lt;/p&gt;

&lt;p&gt;The broader Pro command surface was &lt;strong&gt;not&lt;/strong&gt; fully validated in that environment. During CLI testing, the Pro plugin hit a fatal because of a missing &lt;code&gt;NullTaskScheduler&lt;/code&gt; class. So the larger command blocks below should be read as Pro-oriented workflow examples unless they have been verified on the target site.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical R2 workflow
&lt;/h2&gt;

&lt;p&gt;Cloudflare R2 comes up often for WordPress media for an obvious reason: egress.&lt;/p&gt;

&lt;p&gt;If you serve a lot of images, egress is often the part that gets expensive in a hurry. R2 changes that conversation. It does not make storage free, and it does not remove operational mistakes, but it does change the cost model enough that teams keep revisiting it.&lt;/p&gt;

&lt;p&gt;Official pricing reference: &lt;a href="https://developers.cloudflare.com/r2/pricing/" rel="noopener noreferrer"&gt;Cloudflare R2 Pricing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a Pro-oriented example flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add and activate the R2 account
&lt;/h3&gt;

&lt;p&gt;Before you run this, replace the placeholders with real values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;R2_ACCESS_KEY&lt;/code&gt;: your R2 access key ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R2_SECRET_KEY&lt;/code&gt;: the matching secret&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R2_ACCOUNT_ID&lt;/code&gt;: your Cloudflare account ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You usually get them from the Cloudflare dashboard while creating the R2 API token and bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync accounts add &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloudflare_r2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Production R2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-media-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-key-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$R2_ACCESS_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-access-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$R2_SECRET_KEY&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--api-endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&lt;span class="nv"&gt;$R2_ACCOUNT_ID&lt;/span&gt;.r2.cloudflarestorage.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--custom-domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;media.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Test it before you do anything expensive
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync accounts &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where a lot of bad migrations should stop. Wrong endpoint, wrong key pair, wrong bucket policy, clock skew: better to find it here than halfway through a queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Apply baseline settings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync settings &lt;span class="nb"&gt;set &lt;/span&gt;createObjectOnFileUpload &lt;span class="nb"&gt;yes
&lt;/span&gt;wp cloudsync settings &lt;span class="nb"&gt;set &lt;/span&gt;rewriteFileUrlWithObjectUrl &lt;span class="nb"&gt;yes
&lt;/span&gt;wp cloudsync settings &lt;span class="nb"&gt;set &lt;/span&gt;uploadBatchSize 25
wp cloudsync settings &lt;span class="nb"&gt;set &lt;/span&gt;uploadConcurrency 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would start conservatively. Teams love turning concurrency up too early, then acting surprised when the host or provider becomes the bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sync existing media
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--batch-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Stop and look at the system
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync status &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json
wp cloudsync queue status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your automation cannot pause and make someone look at state, it is not mature enough yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsa9ecxgafqwql1xsuny.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsa9ecxgafqwql1xsuny.webp" alt="Migration workflow" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration is where people get nervous
&lt;/h2&gt;

&lt;p&gt;Fair enough. They should.&lt;/p&gt;

&lt;p&gt;Most teams are not setting this up from scratch. They are moving from an existing plugin and trying not to break URLs, image rendering, or download paths along the way.&lt;/p&gt;

&lt;p&gt;For teams moving from WP Offload Media, a Pro migration flow can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync migrate detect
wp cloudsync migrate import &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wp-offload-media
wp cloudsync accounts list
wp cloudsync accounts &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;imported-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the part I would scrutinize hardest during evaluation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can it import metadata from the current plugin?&lt;/li&gt;
&lt;li&gt;can it verify the imported account cleanly?&lt;/li&gt;
&lt;li&gt;can you keep serving media safely during the transition?&lt;/li&gt;
&lt;li&gt;can you move configuration out of the admin UI after cutover?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A direct comparison is here: &lt;a href="https://1teamsoftware.com/cloudsync-master-vs-wp-offload-media/" rel="noopener noreferrer"&gt;CloudSync Master vs WP Offload Media&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current provider support note
&lt;/h2&gt;

&lt;p&gt;As of March 5, 2026, the WordPress.org listing for WP Offload Media Lite includes Amazon S3, DigitalOcean Spaces, and Google Cloud Storage, but not Cloudflare R2. For teams that want to use R2, that difference matters.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://wordpress.org/plugins/amazon-s3-and-cloudfront/" rel="noopener noreferrer"&gt;WP Offload Media Lite on WordPress.org&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardening step people skip
&lt;/h2&gt;

&lt;p&gt;One of the better habits in infrastructure work is moving from "it works in the dashboard" to "this is managed configuration now."&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;configure the account&lt;/li&gt;
&lt;li&gt;validate it&lt;/li&gt;
&lt;li&gt;export the hardened configuration&lt;/li&gt;
&lt;li&gt;move the source of truth to &lt;code&gt;wp-config.php&lt;/code&gt; or environment variables&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Pro, that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cloudsync config generate &lt;span class="nt"&gt;--account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mf4ydk0jwga5dyhzbou.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mf4ydk0jwga5dyhzbou.webp" alt="Configuration hardening" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not the flashiest part of the workflow, but it is the bit that makes audits, environment parity, and incident response less annoying later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few mistakes I keep seeing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Treating prompts as process
&lt;/h3&gt;

&lt;p&gt;A good prompt is not the asset. The asset is a repeatable workflow with guardrails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rolling out to production first
&lt;/h3&gt;

&lt;p&gt;No. Use staging with representative media first. Always.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding uncertainty
&lt;/h3&gt;

&lt;p&gt;If you have not tested the command on a real site, say that. Developers can handle uncertainty. What they hate is fake certainty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaving rollback vague
&lt;/h3&gt;

&lt;p&gt;"We can just revert it" is not a rollback plan.&lt;/p&gt;

&lt;p&gt;Write down the exact rollback steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;switch active account back&lt;/li&gt;
&lt;li&gt;disable URL rewriting if needed&lt;/li&gt;
&lt;li&gt;keep fallback behavior intact until samples are clean&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple evaluation checklist
&lt;/h2&gt;

&lt;p&gt;If I were choosing a WordPress media offload plugin today, I would ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can I operate it through CLI instead of through screenshots and memory?&lt;/li&gt;
&lt;li&gt;Can I test an account before migration?&lt;/li&gt;
&lt;li&gt;Can I migrate from WP Offload Media or another existing plugin?&lt;/li&gt;
&lt;li&gt;Can I move config into &lt;code&gt;wp-config.php&lt;/code&gt; or env vars?&lt;/li&gt;
&lt;li&gt;Can I start with the free version before committing to Pro?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one matters more than people admit.&lt;/p&gt;

&lt;p&gt;You can start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/wp-cloudsync-master/" rel="noopener noreferrer"&gt;CloudSync Master Free on WordPress.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://1teamsoftware.com/product/cloudsync-master-pro/" rel="noopener noreferrer"&gt;CloudSync Master Pro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://1teamsoftware.com/ai-agent-offload-configuration/" rel="noopener noreferrer"&gt;Automating WordPress Media Offloading with AI Agents and WP-CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://1teamsoftware.com/wordpress-cloudflare-r2-media-offload/" rel="noopener noreferrer"&gt;Cloudflare R2 WordPress media offload guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://1teamsoftware.com/cloudsync-master-vs-wp-offload-media/" rel="noopener noreferrer"&gt;CloudSync Master vs WP Offload Media&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
      <category>devops</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The 67MB Problem: Building a Lightweight WordPress Media Offload Alternative</title>
      <dc:creator>Dmitry Rechkin</dc:creator>
      <pubDate>Tue, 24 Feb 2026 20:52:11 +0000</pubDate>
      <link>https://dev.to/dmitryrechkin/the-67mb-problem-building-a-lightweight-wordpress-media-offload-alternative-4cbh</link>
      <guid>https://dev.to/dmitryrechkin/the-67mb-problem-building-a-lightweight-wordpress-media-offload-alternative-4cbh</guid>
      <description>&lt;p&gt;We had unused Google Cloud credits sitting around, so I threw WordPress onto &lt;strong&gt;Google Cloud Run&lt;/strong&gt; as a serverless experiment. It went fine until I hit the media library.&lt;/p&gt;

&lt;p&gt;That roadblock led me down a rabbit hole: trying to offload media from WordPress to cloud storage usually means installing heavy, bloated SDKs. Here’s how that discovery led us to build a different kind of WordPress media offload plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you run a CMS in a stateless environment, the biggest headache is always the same: where do the uploads go?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F248j8jx92gbrqiyz7w3m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F248j8jx92gbrqiyz7w3m.webp" alt="Horizontal Scaling Diagram" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To horizontally scale compute instances, your infrastructure has to be stateless. Every image or PDF uploaded to the WordPress media library needs to go straight to an object store like Google Cloud Storage (GCS). Otherwise, your containers drop out of sync, and images start breaking the second a new instance spins up.&lt;/p&gt;

&lt;p&gt;I assumed finding a plugin to connect WordPress to Google Cloud Storage would take five minutes. &lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Discovering SDK Bloat
&lt;/h2&gt;

&lt;p&gt;I started testing existing WordPress offload media plugins. Since I was targeting GCS, my first thought was to grab one built on the official Google Cloud PHP SDK.&lt;/p&gt;

&lt;p&gt;Then I checked the deployment sizes.&lt;/p&gt;

&lt;p&gt;One of the most popular offload plugins weighed &lt;strong&gt;67MB&lt;/strong&gt; installed.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;entire WordPress core&lt;/em&gt; is about 73MB. A single offload plugin was nearly the size of the CMS itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zq4eepy0asmdcm8ilc1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zq4eepy0asmdcm8ilc1.webp" alt="67MB SDK Comparison" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why does a plugin that just moves images from Point A to Point B need to rival the size of WordPress? Two words: SDK Bloat.&lt;/p&gt;

&lt;p&gt;Bundling an official SDK (like the AWS SDK or Google Cloud PHP Client) gets you the methods to upload and delete files. But it also drags in tens of megabytes of boilerplate, dependencies, and API definitions for cloud services you are never going to touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this breaks serverless WordPress:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a standard $5/month VPS, a 67MB plugin slows down WP-Admin a bit. Annoying, but manageable.&lt;/li&gt;
&lt;li&gt;In serverless environments, deployment size dictates performance. Cold start times and deployment speeds depend on keeping the codebase small.&lt;/li&gt;
&lt;li&gt;I also experimented with running WordPress on Vercel (see my &lt;a href="https://github.com/dmitryrechkin/vercel-wordpress" rel="noopener noreferrer"&gt;vercel-wordpress repo&lt;/a&gt;), where serverless functions have a 250MB size limit. WordPress is ~73MB, WooCommerce adds another ~51MB, and one 67MB offload plugin puts you at 191MB — leaving barely 60MB for your theme and every other plugin combined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shipping a 67MB vendor folder just to upload images feels like bad architecture. It increases cold-start latency, slows down deployment pipelines, and adds thousands of files to your security surface area.&lt;/p&gt;

&lt;p&gt;I wanted a lightweight WordPress cloud storage plugin that could offload media without dragging down performance. Since I couldn't find one that was actually lean, I pulled in my team and we decided to build it ourselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Dropping the SDK Entirely
&lt;/h2&gt;

&lt;p&gt;Think about what a WordPress media offload plugin actually needs to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate.&lt;/li&gt;
&lt;li&gt;PUT an object.&lt;/li&gt;
&lt;li&gt;DELETE an object.&lt;/li&gt;
&lt;li&gt;Generate a signed URL for private files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We don't need a unified SDK for that. We just need to make authenticated REST API calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generic SDK clients were replaced with focused Guzzle requests&lt;/span&gt;
&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PUT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cloud_provider_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$authentication_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'body'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file_stream&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use Guzzle for the underlying HTTP requests.&lt;/li&gt;
&lt;li&gt;Uploads happen asynchronously in the background. This keeps the upload process from blocking the main WordPress thread. Nobody wants a slow WP-Admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wrote our own authentication wrappers (handling AWS V4 signatures and Google OAuth) and talked directly to the cloud REST APIs. This let us throw out the bloated SDKs entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Engineering CloudSync Master
&lt;/h2&gt;

&lt;p&gt;That realization turned into &lt;strong&gt;CloudSync Master&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Focusing purely on the necessary API calls brought the installed plugin size down to &lt;strong&gt;under 2.5MB&lt;/strong&gt; (the PRO build compiles to 1.5MB).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwtu16z7m3e5xrorb2mh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwtu16z7m3e5xrorb2mh.png" alt="CloudSync Master General Settings Dashboard" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s roughly 30x smaller than the 67MB behemoth we started with. The codebase is lean, 95% type-safe, and it doesn’t drag down the site’s admin performance.&lt;/p&gt;

&lt;p&gt;The project started as a quick way to run WordPress on Google Cloud Run using free credits. But halfway through, I paused the migration. Storage itself is cheap, but serving those files (bandwidth egress) adds up fast.&lt;/p&gt;

&lt;p&gt;We pivoted to &lt;strong&gt;Cloudflare R2&lt;/strong&gt;. The reason is simple: R2 charges &lt;strong&gt;zero egress fees&lt;/strong&gt; and comes with Cloudflare's global CDN built in. For a media-heavy WordPress site, that's a significant cost difference.&lt;/p&gt;

&lt;p&gt;Switching providers created a new problem: moving existing media from GCS to R2. Downloading gigabytes of files to small server instances just to re-upload them would instantly blow out our local disk space and crash the container.&lt;/p&gt;

&lt;p&gt;Instead, we built a cross-provider migration engine directly into the plugin. It streams files from the old provider into memory, pushes them to the new provider, and rewrites the database URLs, all in the background. Files never touch the local disk.&lt;/p&gt;

&lt;p&gt;This lightweight, API-first approach scaled better than we expected. CloudSync Master now supports 10 providers—Amazon S3, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, Wasabi, and others—inside that same 2.5MB footprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a603s5bg9cvodi97pxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a603s5bg9cvodi97pxt.png" alt="Setup Wizard Providers" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: The Sync Bottleneck (Why Background Queues Matter)
&lt;/h2&gt;

&lt;p&gt;When you're migrating a site with 10,000 existing images, you start to notice how WordPress handles file uploads. &lt;/p&gt;

&lt;p&gt;By default, the process is painfully synchronous. You upload a file. The server processes it, generates 5-7 different thumbnail sizes, and then (if you're using an offload plugin) pushes every single one of those files to the cloud over an external HTTP request. &lt;/p&gt;

&lt;p&gt;If you do this synchronously, the main PHP thread stays blocked the entire time. The WordPress dashboard hangs. If you try to bulk-upload a gallery, PHP hits its &lt;code&gt;max_execution_time&lt;/code&gt; limit, and your server throws a 504 Gateway Timeout.&lt;/p&gt;

&lt;p&gt;To fix this, we completely decoupled the upload process.&lt;/p&gt;

&lt;p&gt;Instead of pushing to the cloud immediately, CloudSync Master queues the job. It uses background scheduled tasks (cron) to validate and push items asynchronously. You can drop 500 images into the media library, close the browser tab, and go get coffee. &lt;/p&gt;

&lt;h3&gt;
  
  
  Giving Users Control Under the Hood
&lt;/h3&gt;

&lt;p&gt;We learned quickly that async "black boxes" frustrate users. If something fails, you need to know what and why. So we built a real-time queue dashboard into the plugin. If a file fails because of a network blip or a provider timeout, you click retry on that item. No restarting the whole batch.&lt;/p&gt;

&lt;p&gt;We also introduced a &lt;strong&gt;Concurrency&lt;/strong&gt; slider. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, the plugin processes 5 items simultaneously.&lt;/li&gt;
&lt;li&gt;If you're on a capable server and need to move a massive library fast, you can crank the concurrency up to 20 parallel uploads. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps bulk uploads from overwhelming cheap shared hosting while letting capable servers push files as fast as they can.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Killing the Configuration Headache (OAuth vs. JSON Keys)
&lt;/h2&gt;

&lt;p&gt;One more thing was bugging us: the setup experience.&lt;/p&gt;

&lt;p&gt;Most WordPress media offload plugins require you to go into AWS IAM or the Google Cloud console, create a service account, assign bucket policies, download a JSON credentials file, and carefully paste strings into WordPress. It takes 15 minutes, and if you miss a bracket in the IAM policy, nothing works.&lt;/p&gt;

&lt;p&gt;We wanted connecting to Cloudflare R2 or Google Cloud Storage to take 10 seconds, not 10 minutes.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Cloudflare R2&lt;/strong&gt;, we built a &lt;strong&gt;Quick Setup&lt;/strong&gt; flow (PRO only — someone had to write and debug all that token plumbing). You click a button, the plugin opens Cloudflare's token creation page with the right permissions pre-filled, you create the token and paste it back. Buckets auto-populate in a dropdown. Cloudflare doesn't offer OAuth like Google does, but this is as close to one-click as their platform allows.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Google Cloud&lt;/strong&gt;, we registered an OAuth app and integrated it into the PRO version — that part alone took more work than you'd expect. But now you just click "Sign in with Google", authorize, and your buckets appear. No creating service accounts, no navigating the IAM console (which feels like it requires a PhD), no downloading JSON key files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwdzhlc581lf1mz3okai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpwdzhlc581lf1mz3okai.png" alt="Google Cloud Setup" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No copy-pasting IAM policies. No credential files floating around. We still support manual Access Keys for users who need that level of control, but most people pick the quick flow and move on. I don't like clicking through 15 setup screens either -- that was the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: The WooCommerce Edge Case (Signed URLs)
&lt;/h2&gt;

&lt;p&gt;WooCommerce support is a whole separate beast and deserves its own post, but one bug we hit is worth sharing here because it's a good architectural lesson.&lt;/p&gt;

&lt;p&gt;When you need to serve &lt;em&gt;private&lt;/em&gt; digital products, things get tricky. We ran into this integrating with WooCommerce digital downloads. Customers were buying products, but clicking the download link returned &lt;strong&gt;403 Forbidden&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Root Cause:&lt;/strong&gt;&lt;br&gt;
If you serve public assets, you typically map a custom domain to your bucket (e.g., &lt;code&gt;cdn.yourstore.com&lt;/code&gt; -&amp;gt; &lt;code&gt;public-bucket&lt;/code&gt;). But when you generate a temporary Signed URL to securely deliver a protected zip file, that file lives in a &lt;em&gt;private&lt;/em&gt; bucket. &lt;/p&gt;

&lt;p&gt;Our URL generation logic was accidentally bucket-agnostic. When it generated a Signed URL for a file in the private bucket, it was applying the custom domain from the public bucket. The request would hit the public proxy, looking for a private file, and fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Bucket-Aware Domains&lt;/strong&gt;&lt;br&gt;
We had to rewire how our &lt;code&gt;S3Credentials&lt;/code&gt; class handled routing. Instead of globally applying a custom domain, we implemented strict &lt;strong&gt;bucket-aware custom domains&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, when WooCommerce asks the plugin for a secure download link, the system checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this file in the primary public bucket? If yes, apply the custom domain.&lt;/li&gt;
&lt;li&gt;Is this file in a private bucket? If yes, bypass the custom domain completely and fall back to the provider's direct API endpoint (like the Cloudflare R2 API) to generate a valid, cryptographically signed URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A small routing change, but it's the difference between a working checkout and customers filing support tickets.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;We started this project to run WordPress on Google Cloud Run. We ended up rethinking how offload plugins should be built.&lt;/p&gt;

&lt;p&gt;If you're pushing WordPress into serverless or containerized hosting, plugin size matters more than most people think. The vendor SDKs are convenient, but they come with real costs: cold-start latency, deployment bloat, and thousands of extra files in your attack surface.&lt;/p&gt;

&lt;p&gt;What worked for us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Skip the SDKs. Direct REST API calls with Guzzle handle everything an offload plugin needs to do, at a fraction of the size (67MB down to 2.5MB).&lt;/li&gt;
&lt;li&gt;Queue everything. Background cron keeps uploads from blocking the WordPress admin thread.&lt;/li&gt;
&lt;li&gt;Make setup fast. If connecting a cloud provider takes 15 minutes, something is wrong.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you're dealing with plugin bloat or looking for a lightweight WP Offload Media alternative, &lt;a href="https://wordpress.org/plugins/wp-cloudsync-master/" rel="noopener noreferrer"&gt;CloudSync Master&lt;/a&gt; is free on WordPress.org. Full setup guides and WP-CLI reference are in the &lt;a href="https://github.com/1TeamSoftware/docs/tree/main/plugins/wp-cloudsync-master" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the architecture, serverless WordPress, or cloud provider migration in the comments.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>serverless</category>
      <category>php</category>
    </item>
    <item>
      <title>AI E-Mail Event Booking Agent</title>
      <dc:creator>Dmitry Rechkin</dc:creator>
      <pubDate>Sun, 01 Sep 2024 04:57:23 +0000</pubDate>
      <link>https://dev.to/dmitryrechkin/ai-e-mail-event-booking-agent-16oh</link>
      <guid>https://dev.to/dmitryrechkin/ai-e-mail-event-booking-agent-16oh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/nylas"&gt;Nylas Challenge&lt;/a&gt;: AI Expedition.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built and Why
&lt;/h2&gt;

&lt;p&gt;As a member of the strata council, I regularly handle reservations for the amenity room. While the current volume of bookings isn’t overwhelming, it does come with its own set of challenges—negotiating time slots, enforcing reservation rules, ensuring the completion of PDF forms, verifying them, and then manually booking the room.&lt;/p&gt;

&lt;p&gt;With more residents moving into our building, I began to think about automating this process. However, communicating new methods, such as using specific URLs for reservations, might not work for everyone. That's when the idea of building an AI agent to manage reservations started taking shape in my mind. The Nylas challenge was the final push I needed to put aside other projects and dive into this one.&lt;/p&gt;

&lt;p&gt;Nylas offers the exact tools I was looking for, so I decided to create something practical that we can actually use in our building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Driven by the motto “go PRO or go home,” I might have gone a bit overboard with the video. The real action starts at the 2:30 mark and lasts about 2 minutes.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/watch?si=shfVYjWjPWrdIO_k&amp;amp;%3Bt=170&amp;amp;v=L_Rw30v6bg0&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The source code for the AI email event booking agent can be found on &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dmitryrechkin" rel="noopener noreferrer"&gt;
        dmitryrechkin
      &lt;/a&gt; / &lt;a href="https://github.com/dmitryrechkin/ai-email-event-booking-agent" rel="noopener noreferrer"&gt;
        ai-email-event-booking-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AI Email Event Booking Agent&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This project implements an AI-powered Email Event Booking Agent designed to automate the process of handling incoming event booking requests via email. The agent intelligently processes emails, validates the information provided, requests additional details if necessary, checks calendar availability, fills out PDF forms, and books events.&lt;/p&gt;
&lt;p&gt;This solution is optimized to run on serverless Cloudflare Workers, aiming to minimize external dependencies for enhanced speed and efficiency. Many packages have been custom-written to support this goal, ensuring that the solution remains lightweight and fast.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Key Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Architecture&lt;/strong&gt;: Runs on Cloudflare Workers, leveraging Cloudflare KV for storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nylas API Integration&lt;/strong&gt;: Handles incoming emails via the Nylas API, transforming them into a common format for AI processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic AI Agent&lt;/strong&gt;: Utilizes AI to decide which tools to use based on natural language instructions and a customizable JSON schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Dependencies&lt;/strong&gt;: Custom code replaces…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dmitryrechkin/ai-email-event-booking-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Your Journey
&lt;/h2&gt;

&lt;p&gt;I'm a huge fan of serverless technology, particularly Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;Initially, I planned to take advantage of the &lt;a href="https://www.npmjs.com/package/nylas" rel="noopener noreferrer"&gt;Nylas SDK&lt;/a&gt;. However, I quickly ran into issues because it wasn’t particularly compatible with Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;No big deal, I figured. Nylas has well-documented APIs, which are explained in detail &lt;a href="https://developer.nylas.com/docs/api/v3/ecc/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The challenge was understanding which combination of APIs would best suit my needs.&lt;/p&gt;

&lt;p&gt;At first, I decided to use the &lt;a href="https://developer.nylas.com/docs/api/v3/ecc/#post-/v3/grants/-grant_id-/events" rel="noopener noreferrer"&gt;Create Events API&lt;/a&gt; and the &lt;a href="https://developer.nylas.com/docs/api/v3/ecc/#post-/v3/grants/-grant_id-/messages/send" rel="noopener noreferrer"&gt;Send Message API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, I discovered the Nylas Scheduler API, which seemed perfect for my AI Event Booking Agent. You can learn more about it &lt;a href="https://developer.nylas.com/docs/v3/scheduler/using-scheduler-api/#create-a-configuration-object" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, I quickly realized that the requirements to create a configuration object, followed by a session, might be too complex for an AI to handle. So, I reverted to my original plan of using the Email, Calendar, and Event APIs separately.&lt;/p&gt;

&lt;p&gt;Although the webhook provides a snippet of the email, I realized that the body of the email is often in HTML and can contain previous messages embedded within it. This could potentially confuse the AI agent, so I embarked on a journey to find a library that could extract just the latest email body as text, making it easier to feed to the LLM.&lt;/p&gt;

&lt;p&gt;After much searching, I found that nothing worked as expected or required Node.js. So, I implemented my own solution, which turned out to be pretty decent and will likely be used in future projects: &lt;a href="https://github.com/dmitryrechkin/text-email-body-parser" rel="noopener noreferrer"&gt;text-email-body-parser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the requirements was to be able to read and fill PDF forms. While this is relatively easy with existing libraries, I needed a set of shortcut classes capable of reading and filling PDF files represented as byte arrays since working with files was out of the question in this case.&lt;/p&gt;

&lt;p&gt;During the process of implementing my abstraction layer on top of the Nylas API, which could be used in a Node.js-less environment, I created two helper packages: &lt;a href="https://github.com/dmitryrechkin/calendar-nylas" rel="noopener noreferrer"&gt;calendar-nylas&lt;/a&gt; and &lt;a href="https://github.com/dmitryrechkin/message-nylas" rel="noopener noreferrer"&gt;message-nylas&lt;/a&gt;. These packages enable the AI Event Booking Agent to perform the necessary tasks for booking events and sending emails.&lt;/p&gt;

&lt;p&gt;As I researched the best way to implement the agent, I chose Vercel’s AI SDK (&lt;a href="https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling" rel="noopener noreferrer"&gt;AI SDK Core&lt;/a&gt;) because it provides a streamlined method for automating tool calls. This allows me to focus on creating tools and refining prompts instead of worrying about the mechanics.&lt;/p&gt;

&lt;p&gt;To make my code easily adaptable for tool use, I created a tiny library, &lt;a href="https://github.com/dmitryrechkin/foundation-core" rel="noopener noreferrer"&gt;foundation-core&lt;/a&gt;, which lets me create actions in a predictable way and use them as tools with automatic input and output validation using Zod.&lt;/p&gt;

&lt;p&gt;Next, I wanted to define a schema for the data to collect, so I could efficiently extract it with AI and then pass it to my tool. While Zod is amazing for this, I couldn't find a pre-existing solution that allowed dynamic schema definition without generating static code.&lt;/p&gt;

&lt;p&gt;This led to the creation of &lt;a href="https://github.com/dmitryrechkin/json-schema-to-zod" rel="noopener noreferrer"&gt;json-schema-to-zod&lt;/a&gt;, which I can use to store data structures as JSON schemas in KV storage and dynamically convert them to Zod schemas for use with AI tools.&lt;/p&gt;

&lt;p&gt;Finally, it was just a matter of putting everything together. I’ve described the architecture in the repository, so I won’t go into it again here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project was an exciting journey into the world of AI-driven automation. The combination of Nylas, Cloudflare Workers, and Vercel’s AI SDK allowed me to build a practical solution that I can use in my own building. It’s fascinating to see how AI can manage something as complex as event bookings, complete with all the quirks and challenges of real-world scenarios.&lt;/p&gt;

&lt;p&gt;While there were a few hurdles along the way, like adapting the Nylas API for Cloudflare Workers and dealing with the intricacies of parsing email bodies, these challenges ultimately led to the creation of several reusable libraries that I’m sure will be beneficial in future projects.&lt;/p&gt;

&lt;p&gt;In the end, the AI Event Booking Agent not only simplifies the reservation process but also opens the door to more sophisticated AI-driven workflows. I’m excited to see how this project evolves and how AI can further enhance everyday tasks in our work and life.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>nylaschallenge</category>
      <category>api</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
