<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Effect Kit blog</title>
    <link>https://effectkit.dev/blog</link>
    <atom:link href="https://effectkit.dev/blog/rss.xml" rel="self" type="application/rss+xml" />
    <description>Release notes, guides and the reasons behind how Effect Kit works.</description>
    <language>en</language>
    <image>
      <url>https://effectkit.dev/logo.svg</url>
      <title>Effect Kit blog</title>
      <link>https://effectkit.dev/blog</link>
    </image>
    <lastBuildDate>Fri, 30 Oct 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>Introducing Effect Kit</title>
      <link>https://effectkit.dev/blog/introducing-effect-kit</link>
      <guid isPermaLink="true">https://effectkit.dev/blog/introducing-effect-kit</guid>
      <description>Our first two packages bring Redis and email into Effect, with errors that say how far a request got before it failed, so you know when to retry.</description>
      <pubDate>Fri, 30 Oct 2026 00:00:00 GMT</pubDate>
      <dc:creator>Vedant Chainani</dc:creator>
      <category>Announcement</category>
      <category>redis</category>
      <category>emails</category>
      <category>effect</category>
      <content:encoded><![CDATA[<p>Effect Kit is a set of open-source packages that bring the infrastructure your app depends on into <a href="https://effect.website">Effect</a>.</p>
<p>This week we published the first two: <code>@effect-kit/redis</code> and <code>@effect-kit/emails</code>.</p>
<h2>The question a Promise can&#x27;t answer</h2>
<p>If you talk to Redis or send email from Effect today, you probably wrap a Promise client in <code>Effect.tryPromise</code>. That works well, right up until a call fails halfway.</p>
<p>The connection drops while you&#x27;re writing. Did Redis apply the <code>INCR</code>? The email provider times out. Is the customer about to get two receipts?</p>
<p>The wrapped Promise can&#x27;t tell you. It knows the request failed. It doesn&#x27;t know how far it got, and that is the one thing you need to decide what to do next.</p>
<p>We built Effect Kit around that question.</p>
<h2>Every failure says how far it got</h2>
<p>In <code>@effect-kit/redis</code>, a dropped connection fails with a <code>ConnectionError</code>, and its <code>delivery</code> field says where the command was when it happened.</p>
<p><code>&quot;unsent&quot;</code> means nothing was written. The server never saw the command, so sending it again is safe.</p>
<p><code>&quot;possibly-written&quot;</code> means the bytes may have reached the server, but no reply came back. The command may have run once, or not at all.</p>
<p>The client never sends a command twice on its own. Retrying is your call, and the error carries what you need to make it:</p>
<pre><code class="language-ts">import { Effect, Schedule } from &quot;effect&quot;;
import { Redis } from &quot;@effect-kit/redis&quot;;

const markPaid = Effect.gen(function* () {
  const redis = yield* Redis;
  return yield* redis.set(&quot;order:42&quot;, &quot;paid&quot;);
}).pipe(
  Effect.retry({
    schedule: Schedule.exponential(&quot;200 millis&quot;),
    times: 5,
    while: (error) =&gt; error._tag === &quot;ConnectionError&quot; &amp;&amp; error.delivery === &quot;unsent&quot;,
  }),
);
</code></pre>
<p>Here&#x27;s that same situation as a machine you can break. Throw the switch before the write, then after it, and watch the error change.</p>
<p></p>
<p>The difference matters more for some commands than others. Setting <code>order:42</code> to <code>&quot;paid&quot;</code> twice is harmless. Incrementing a counter twice is a bug, and nothing in a plain error message will tell you which case you&#x27;re in.</p>
<p>Email has the same problem with a different name. Every <code>EmailError</code> from <code>@effect-kit/emails</code> carries an <code>acceptance</code> field:</p>
<ul>
<li><code>&quot;notSubmitted&quot;</code>: nothing reached the provider. Send it again.</li>
<li><code>&quot;rejected&quot;</code>: the provider refused it. Sending the same message again won&#x27;t help, so fix it first.</li>
<li><code>&quot;unknown&quot;</code>: the provider may have accepted it. Retry only with an idempotency key on a provider that honors one.</li>
</ul>
<p>Some failures never need a request at all. Each provider declares what it can carry, and <code>email.send</code> checks the message against that before calling the provider. Send an attachment through MailerSend and you get an <code>UnsupportedFeatureError</code> with <code>acceptance: &quot;notSubmitted&quot;</code>, before anything leaves your process.</p>
<h2>Swap the Layer, keep the program</h2>
<p>Each package gives you one service, <code>Redis</code> or <code>Email</code>, and a set of Layers that provide it. Your program only ever asks for the service:</p>
<pre><code class="language-ts">import { Effect } from &quot;effect&quot;;
import { Email } from &quot;@effect-kit/emails&quot;;

const welcome = (to: string) =&gt;
  Effect.gen(function* () {
    const email = yield* Email;
    return yield* email.send({
      from: &quot;hello@example.com&quot;,
      to,
      subject: &quot;Welcome&quot;,
      text: &quot;Your account is ready.&quot;,
    });
  });
</code></pre>
<p>Where the email actually goes is decided once, at the edge of the app:</p>
<pre><code class="language-ts">import { Effect } from &quot;effect&quot;;
import { Email } from &quot;@effect-kit/emails&quot;;
import * as Resend from &quot;@effect-kit/emails/resend&quot;;
import * as Postmark from &quot;@effect-kit/emails/postmark&quot;;
import { EmailTest } from &quot;@effect-kit/emails/testing&quot;;

const welcome = Effect.gen(function* () {
  const email = yield* Email;
  return yield* email.send({
    from: &quot;hello@example.com&quot;,
    to: &quot;ada@example.com&quot;,
    subject: &quot;Welcome&quot;,
    text: &quot;Your account is ready.&quot;,
  });
});

// ---cut---
welcome.pipe(Effect.provide(Resend.layerConfig()));
welcome.pipe(Effect.provide(Postmark.layerConfig()));
welcome.pipe(Effect.provide(EmailTest.layer()));
</code></pre>
<p>Moving from Resend to Postmark is a one-line change. So is a test: <code>EmailTest.layer()</code> runs the same validation and capability checks as a real provider, then records the message in an outbox you can assert on instead of sending it.</p>
<p>Redis works the same way across runtimes. <code>RedisNode.layer</code> and <code>RedisBun.layer</code> open native sockets, and <code>RedisUpstash.layerConfig</code> talks HTTP for places without them, like Deno and Cloudflare Workers.</p>
<p></p>
<h2>Try it</h2>
<p>Both retry examples are in the playground below, under the <code>Effect.retry</code> tab of each package, next to pipelines, scopes, batches and capability checks. They run against a simulated Redis and a simulated email provider, so you can pick where things fail.</p>
<h2>What&#x27;s in the first release</h2>
<p><strong>Redis.</strong> A native client that speaks RESP itself, so Effect is its only dependency. Commands are typed, connections are scoped and close with your program, and it covers pipelines, transactions, Pub/Sub, Streams, Cluster and Sentinel. It runs against Redis, Valkey, Dragonfly, KeyDB, Redict, Garnet, Apache Kvrocks and Upstash. It&#x27;s already on 0.1.1, which added typed <code>batch</code> and reusable <code>compile</code> plans. The <a href="https://effectkit.dev/docs/redis">quick start</a> takes a few minutes.</p>
<p><strong>Emails.</strong> One <code>Email</code> service across 16 providers, including Resend, Amazon SES, Postmark, SendGrid, Mailgun and SMTP. You install only the SDK for the provider you use. <code>EmailReact.render</code> turns a React Email component into HTML and plain-text bodies, and <code>email.sendMany</code> sends up to 1,000 messages with bounded concurrency, returning one result per message. Start with the <a href="https://effectkit.dev/docs/emails">quick start</a>.</p>
<h2>What&#x27;s next</h2>
<p>Storage is next: put, scoped get, head, delete and paginated listing, in the same shape as the other two. One service, a Layer per backend, and errors you can act on.</p>
<p>Effect Kit is Apache-2.0 and developed in the open on <a href="https://github.com/effect-kit">GitHub</a>. If something is missing or broken, open an issue. Updates go out on X at <a href="https://x.com/effectkit">@effectkit</a> and here on the blog.</p>
<p>Thanks for reading. Now go break a connection on purpose.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
