Upyo 0.6.0: MIME composition, streaming attachments, and calendar invitations #75
dahlia
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Upyo is a cross-runtime email library for JavaScript that provides a unified API for sending email across Node.js, Deno, Bun, and edge functions. It supports SMTP, JMAP, and HTTP email providers through the same
Transportinterface.Upyo 0.6.0 gives applications more control over the messages they send. You can compose MIME without connecting to a server, stream large attachments through SMTP, send calendar invitations, and choose message identifiers for tracking replies. This release also adds Maileroo and Mailtrap transports, LogTape logging, and several SMTP extensions.
MIME composition and raw delivery
The new @upyo/mime package separates message composition from delivery.
composeMessage()turns an Upyo message into replayable MIME bytes, with optional DKIM signing. You can save an .eml file, inspect the message before sending it, or pass the result directly to SMTP or JMAP:The generated date, message identifier, and multipart boundaries stay the same on every read. Bcc recipients belong to the delivery envelope; the serialized message has no Bcc header. Composition works in edge runtimes with Web Crypto, including Cloudflare Workers without Node.js compatibility.
Both
SmtpTransport.sendRaw()andJmapTransport.sendRaw()also accept MIME created elsewhere, with an explicit envelope. This is useful for forwarding signed or encrypted messages. SMTP raw delivery preserves the supplied headers and bypasses the transport's DKIM settings, so sign during composition if you need a signature. A JMAP server may modify imported MIME during submission; byte-for-byte preservation is not guaranteed there. Raw sends are not retried automatically, to avoid duplicate delivery when the outcome is uncertain.See the MIME composition guide for streaming output, signing, and attachment lifetime rules, and the raw SMTP guide for delivering existing MIME.
Installation
Streaming attachments
Attachments now accept
Blobobjects and replayable async content factories, in addition to byte arrays and promises.createMessage()retains aFilewithout reading it immediately. A factory opens a fresh source each time its content is needed, so a retry can read the attachment again.SMTP encodes attachments incrementally and applies backpressure while writing the message. Large files no longer have to become one large encoded buffer before delivery. If you use DKIM, set
dkim.bodyModeto"streaming"for bounded attachment memory: Upyo reads the source once to hash it and again to send it. The default"buffered"mode keeps a single read. Sources must produce the same bytes on each read; a replay mismatch fails the send before acceptance.HTTP provider transports accept the new content forms too, but still buffer attachment content to build their provider payloads. See the attachment guide for factories, cancellation, and examples for each runtime.
Calendar invitations
A calendar invitation needs a
text/calendarpart with the right scheduling method. The newcalendarfield lets Upyo compose that part from an iCalendar object your application supplies:SMTP and JMAP compose a calendar body alternative alongside the text and HTML bodies. HTTP provider transports send an invite.ics attachment instead, so calendar-client scheduling behavior is not guaranteed with those transports.
Upyo reads the method from the object's own
METHODproperty and validates its content-line syntax. It does not generate events or manage their scheduling state. Your application remains responsible for the organizer and attendees, event identifiers, time zones, and sequence numbers for updates or cancellations. The calendar invitation guide explains these requirements and the differences between transports.Message identity and reply threading
Applications that match incoming replies to tickets or conversations need to know an outgoing message's identifier before it leaves.
MessageandcreateMessage()now acceptmessageId,date,inReplyTo, andreferences:SMTP and JMAP support all four fields. Most HTTP provider transports forward
inReplyToandreferencesas headers, but omitmessageIdanddatewhere the provider does not document whether supplied values survive. Check the message identity guide for the provider support table.Receipt.messageIdstill means the delivery handle reported by the transport or provider. It is not the RFC 5322Message-IDcarried by the email. Store the explicit field when you need to correlate replies.SMTP extensions and connection verification
SMTP gains support for several extensions that applications previously had to work around:
PIPELININGsendsMAIL FROMand recipient commands together when the server supports it, reducing round trips.SIZEdeclares known encoded message sizes and checks server limits. Streaming messages whose size is not known in advance are checked during transmission.DSNlets callers request delivery status notifications, including per-recipient notification settings and original recipient addresses.SMTPUTF8supports internationalized addresses with UTF-8 local parts and Unicode domains. Address parsing in @upyo/core accepts them too; SMTP delivery requires the server to advertiseSMTPUTF8and8BITMIME.5.1.1in structured failure metadata, while retaining the original text.You can also override the SMTP envelope independently of the visible message headers. This supports separate bounce addresses, null reverse-paths, and per-message envelope resolvers for bulk delivery.
The new
verify()method checks configuration without sending a message:SMTP verification checks a fresh connection, TLS policy, and configured authentication, then closes the connection. JMAP has
verify()as well, checking its session, account, drafts mailbox, and identities. Both reject on failure. For code that accepts an arbitrary transport,isVerifiableTransport()detects this optional capability.The
requireTlsoption requires a successful STARTTLS upgrade, even when the server does not advertise it. Deno users need Deno 2.7.13 or later for STARTTLS. See the SMTP guide for the extensions and the JMAP guide for verification.Maileroo and Mailtrap transports
The new @upyo/maileroo package supports Maileroo's HTTP sending API, including attachments, inline images, custom headers, tags, and tracking settings.
sendMany()sends messages sequentially, with retries and structured failure receipts.@upyo/mailtrap supports both Mailtrap's Email API for delivery and Email Sandbox for testing. It includes batch sending, attachments, categories, and custom variables. To send to a sandbox inbox:
Both use the same
send()andsendMany()interface as the existing transports. See the Maileroo guide and Mailtrap guide for configuration. Thanks to @narekhovhannisyan for contributing the Mailtrap transport in #32.Installation
Install the package for the provider you use:
Replace
@upyo/maileroowith@upyo/mailtrapfor Mailtrap.LogTape transport
@upyo/logtape records email delivery events through LogTape. With no wrapped transport, it logs sends and returns synthetic successful receipts without sending mail. Wrap another transport to log real delivery while preserving its receipts and errors:
The application controls LogTape's sinks, filters, and levels. Full-message recording is optional, including a development format that puts subjects and bodies into readable logs. The LogTape transport guide covers log-only use and composition with other decorators.
Installation
Upgrading from 0.5
Most applications can keep their existing message creation and sending code. There are a few changes to account for if you use lower-level APIs or implicit SMTP defaults:
readAttachmentContent()oriterateAttachmentContent()instead of awaitingattachment.contentdirectly. Attachment content can now be aBlobor factory.JmapEmailCreateno longer has aheadersproperty. JMAP custom headers are represented by individualheader:properties, as the protocol requires.securefrom the port: port 465 uses implicit TLS; other ports start with plaintext and upgrade through STARTTLS when advertised. Setsecure: trueexplicitly for implicit TLS on a nonstandard port.poolnow enables the documented default oftrue. Dispose an SMTP transport withawait usingor callcloseAllConnections()when finished so pooled connections do not keep your application running.This release also fixes DKIM verification for Ed25519 signatures and
simpleheader canonicalization, MIME text encoding, and Resend attachment Base64 padding. The changelog includes the smaller corrections and provider-specific behavior changes.Thanks
A special thank-you to @gerardp, whose feature proposals shaped much of this release. The SMTP extension requests (#42, #43, #44, #45, and #46), envelope overrides (#52), streaming attachments (#56), message identity (#58), calendar invitations (#63), raw delivery (#64), connection verification (#67), and standalone MIME composition (#68) all came from those discussions. That sustained attention to what applications need from an email library made a real difference to Upyo 0.6.0. Thank you for taking the time to propose these features and work through the details with us.
For the complete changelog and technical details, see CHANGES.md.
For questions or issues, please visit our GitHub repository.
All reactions