← Back to archive

Repo of the Day

dahlia/upyo: Upyo is a simple and cross-runtime library for sending email messages using SMTP and various email providers. It works on Node.js, Deno, Bun, and edge functions.

Published: Sep 16, 2026

Open repository ↗

Upyo is a simple and cross-runtime library for sending email messages using SMTP and various email providers. It works on Node.js, Deno, Bun, and edge functions. - dahlia/upyo

Summary

Upyo is a TypeScript email library that exposes one consistent API for sending mail through SMTP, JMAP, or several HTTP-based providers, and it runs on Node.js, Deno, Bun, and edge functions. The project is organized as a monorepo with a shared @upyo/core package, individual per-provider transport packages, and add-ons for retries, observability, and testing. It is released under the MIT license.

What it is useful for

Upyo is useful when a JavaScript or TypeScript service needs to send transactional email but the team wants to avoid hard-coding against a single vendor. The same createMessage and transport.send flow works whether messages are delivered through Amazon SES, Mailgun, Resend, SendGrid, Mailtrap, Maileroo, Lettermint, Plunk, JMAP, or plain SMTP, so changing providers or supporting multi-cloud deployments does not require rewriting application code.

For testing, @upyo/mock captures messages without real network calls. @upyo/retry wraps any transport with a backoff decorator, and @upyo/opentelemetry and @upyo/logtape add tracing and logging hooks. @upyo/mime handles portable MIME composition and DKIM signing across runtimes, which is relevant for edge deployments where Node-specific helpers are not available.

How engineers can use it

The README's example demonstrates the typical flow: build a message with createMessage from @upyo/core, instantiate a transport such as MailgunTransport from @upyo/mailgun using credentials pulled from environment variables, attach files via the standard File constructor, and call transport.send(message). The returned receipt has a successful flag, a messageId on success, and an errorMessages array on failure, which makes it straightforward to handle both outcomes with one code path.

Packages are published on both JSR and npm, so the import syntax and install command depend on the target runtime and registry. The README does not show explicit install commands, so the canonical install line for a given package is best confirmed on its JSR or npm page. For non-trivial setups such as DKIM keys, JMAP account configuration, or OpenTelemetry exporters, the README directs readers to the full documentation at upyo.org.