npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-smtp-tcp

v1.0.0

Published

SMTP client for React Native (message submission over TLS/STARTTLS with AUTH and MIME)

Readme

react-native-smtp-tcp

An SMTP client for React Native. It speaks the SMTP submission protocol in TypeScript over a native TCP/TLS socket, so an app can send email directly through a user-configured SMTP account without a backend or a third-party email API.

Mature JavaScript SMTP clients (nodemailer and similar) are Node-only and cannot bundle under React Native, because Metro cannot resolve Node's net, tls, and dns. This library implements the protocol itself and delegates the socket and TLS to the react-native-tcp-socket native module.

Features

  • Implicit TLS on port 465 and STARTTLS on port 587.
  • AUTH PLAIN, LOGIN, and XOAUTH2 (with a pluggable OAuth2 token provider).
  • A MIME builder for text, HTML (multipart/alternative), and attachments (multipart/mixed), with RFC 2047 encoded-words, SMTPUTF8 and 8BITMIME gating, dot-stuffing, and header folding.
  • CR, LF, and NUL are rejected in user-controlled fields (SMTP command and header injection).
  • Layered timeouts and caps on reply parsing; a message is reported as sent only after the server confirms it.
  • A verify() probe to test an account and network before sending.

TLS certificate and hostname verification is on by default (see Setup); an optional certificate pin is available.

Requirements and install

This library uses a native socket module, so it needs a development build or a prebuild. It does not run in Expo Go.

npm install react-native-smtp-tcp react-native-tcp-socket

react-native and react-native-tcp-socket are peer dependencies.

Setup

The native transport validates the certificate chain, but hostname verification on Android must be enabled at build time. Enable it with the config plugin (Expo) or the patch (bare React Native). This is a one-time setup step.

Expo

Add this package to the plugins array in your app config. During npx expo prebuild (and EAS Build) it enables hostname verification in the native transport:

{
  "expo": {
    "plugins": ["react-native-smtp-tcp"]
  }
}

Then build a development build. It does not run in Expo Go.

Bare React Native

Use patch-package. Copy node_modules/react-native-smtp-tcp/patches/react-native-tcp-socket+6.4.1.patch into your project's patches/ directory, add a postinstall script, and run pod install on iOS:

{
  "scripts": {
    "postinstall": "patch-package"
  }
}

patch-package reapplies the patch on every install, and autolinking picks up react-native-tcp-socket.

CA certificates

Prefer passing a CA as an inline PEM string (see tls.ca). If you import a .pem/.p12 as an asset instead, add those extensions to metro.config.js resolver.assetExts.

Quick start

import { createTransport } from 'react-native-smtp-tcp';

const transport = createTransport({
  host: 'mail.example.com',
  port: 465,
  secure: 'implicit',
  auth: { user: '[email protected]', pass: 'app-password' },
});

await transport.verify(); // optional: check the account and network

const info = await transport.sendMail({
  from: { name: 'Me', address: '[email protected]' },
  to: [{ address: '[email protected]' }],
  subject: 'Rappel: café',
  text: 'Bonjour.',
  html: '<p>Bonjour.</p>',
});

console.log(info.messageId, info.accepted);

API reference

createTransport(options): Transport

Validates and resolves the options (rejecting prototype-pollution keys) and returns a Transport. Options:

| Option | Type | Default | Meaning | | --- | --- | --- | --- | | host | string | required | Server hostname, or a bare IP (see tls.servername). | | port | number | 465 or 587 | Submission port. Port 25 is rejected. | | secure | 'implicit' \| 'starttls' \| 'auto' | 'auto' | Transport security. auto resolves from the port and defaults to implicit TLS on 465. | | requireTLS | boolean | true | Abort rather than send anything in cleartext. | | auth | see below | none | Credentials. | | tls | TlsOptions | see below | TLS options. | | timeouts | { connectMs, greetingMs, idleMs, overallMs } | 15000 / 15000 / 30000 / 60000 | Layered timeouts in milliseconds. | | caps | { maxLineBytes, maxReplyBytes, maxContinuationLines, maxQueuedReplies } | 8192 / 65536 / 200 / 16 | Caps for reply parsing. maxQueuedReplies bounds unsolicited replies buffered while no command is pending. | | logger | Logger | none | Optional debug logger. Credentials are redacted. | | clientId | string | [127.0.0.1] | EHLO/HELO client identity (FQDN or address literal). |

auth is one of:

{ user: string; pass: string }                                  // PLAIN / LOGIN
{ user: string; type: 'oauth2'; accessToken: string }           // XOAUTH2 (static token)
{ user: string; type: 'oauth2'; tokenProvider: () => Promise<string> | string } // XOAUTH2 (refresh hook)

tls (TlsOptions):

| Option | Type | Meaning | | --- | --- | --- | | ca | string | Inline CA PEM to trust a private or self-signed server. | | pinnedCertSha256 | string | Optional SHA-256 fingerprint of the leaf certificate (colon-separated or plain hex), checked after the handshake. | | servername | string | Expected certificate identity. Required when host is a bare IP. | | minVersion | 'TLSv1.2' \| 'TLSv1.3' | Minimum TLS version. Defaults to TLS 1.2. | | cert, key | string | Client certificate and key (PEM) for mutual TLS. |

transport.verify(): Promise<{ capabilities }>

Connects, negotiates TLS, sends EHLO, authenticates, and quits. Use it to test a new account or diagnose a network. Throws on failure.

transport.sendMail(message): Promise<SendInfo>

Builds and sends one message. Returns { accepted, rejected, response, messageId }. Throws (without reporting the message as sent) on any protocol, TLS, or connection failure. message:

| Field | Type | Meaning | | --- | --- | --- | | from | string \| { name?, address } | Sender. Also used as the envelope MAIL FROM. | | to | Array<string \| { name?, address }> | Recipients. At least one required. | | cc, bcc | same as to | Additional recipients. bcc is in the envelope only, never in a header. | | replyTo | string \| { name?, address } | Optional Reply-To. | | subject | string | Subject. Non-ASCII is RFC 2047 encoded. | | text, html | string | Plain and/or HTML body. Both produce multipart/alternative. | | attachments | Array<{ filename, content, contentType?, encoding?, contentId? }> | content is bytes or a base64 string (set encoding: 'base64'). | | headers | Record<string, string> | Extra headers. Managed headers cannot be overridden. | | messageId, date | string, Date | Optional overrides. A unique Message-ID and the current Date are generated otherwise. |

transport.close(): Promise<void>

Present for API symmetry. This client opens a fresh connection per operation and closes it when the operation finishes, so there is nothing persistent to close.

Errors

All errors extend SmtpError and carry a transient flag (true when a retry may succeed). The subclasses are SmtpConfigError, SmtpMessageError, SmtpProtocolError, SmtpSecurityError, SmtpAuthError, SmtpTimeoutError, and SmtpConnectionError. Authentication failures map to a single generic message and do not reveal whether the user or the password was wrong.

TLS notes

  • Certificate chain and hostname verification is on by default. Node uses rejectUnauthorized; on device the config plugin / patch enables native endpoint identification, so the handshake rejects a certificate whose SAN/CN does not match the host. There is no option to disable validation. If the handshake fails, the connection aborts before any credential is sent.

  • Private or self-signed servers: pass the server's CA as an inline PEM string in tls.ca, or pin the leaf certificate with tls.pinnedCertSha256. To compute the pin:

    openssl x509 -in server.crt -noout -fingerprint -sha256
  • Bare-IP hosts: when host is an IP address, set tls.servername to the expected certificate identity.

  • OAuth2: major providers are retiring Basic Auth for SMTP (Google in 2025, Microsoft 365 for SMTP-AUTH Basic Auth). Prefer XOAUTH2 with a tokenProvider so tokens refresh.

  • Credentials: the library does not persist credentials and does not log a password, token, or AUTH payload (AUTH lines are redacted). Store credentials in OS-backed secure storage (Android Keystore / iOS Keychain, for example via Expo SecureStore).

Examples

Runnable snippets are in examples/: implicit TLS with a password, STARTTLS with a password, and OAuth2 with a token provider.

Troubleshooting

  • "Sends on Wi-Fi, fails on cellular." Many mobile carriers and networks block outbound SMTP ports (465, 587, 25). Use verify() to detect it on the current network. The user must switch networks or use a provider API.
  • Gmail: requires an app password (with 2-Step Verification) or OAuth2. Ordinary account passwords do not work. Prefer XOAUTH2.
  • Microsoft 365 / Outlook: Basic Auth for SMTP is being retired; use OAuth2. Some tenants disable SMTP AUTH and require an administrator to enable it.
  • "Certificate rejected for a private server." Provide the server's CA in tls.ca, or pin with tls.pinnedCertSha256.
  • Expo Go: this library needs a native socket module and does not run in Expo Go. Use a development build or npx expo prebuild.

Platform support

  • Android: the config plugin / patch enables hostname verification in the native transport.
  • iOS: react-native-tcp-socket sets the peer name on the default TLS path, so the platform performs hostname verification there.

Known limitations

  • On-device behavior is not exercised by this project's automated tests. The protocol and MIME engines and the Node adapter run in Node against in-process SMTP and TLS servers; the react-native-tcp-socket adapter and the native hostname-verification setup are validated on a real device by the consuming app.
  • react-native-tcp-socket exposes no JavaScript knob for the TLS minimum version, cipher list, or SNI override, so those are governed by the platform TLS stack on device (modern Android/iOS default to TLS 1.2+). The Node adapter sets the minimum version and cipher list pre-handshake; on device the client reads the negotiated protocol version after the handshake and aborts if it is below the configured minimum.
  • The certificate pin is checked after the handshake, because the native module exposes the peer certificate only once the handshake completes.
  • Out of scope: DNS/MX resolution (configure an explicit host), DKIM signing (the submission server handles it), connection pooling, and IMAP/POP.

Building and contributing

The Makefile is the front door; run make (or make help) to list targets.

make install     # install dependencies
make build       # compile to dist/ (ESM + CJS + types)
make test        # run the test suite
make cover       # run tests with coverage (fails under 90%)
make lint        # ESLint plus the disable-validation guard
make typecheck   # type-check without emitting
make pack-check  # build, verify the pack manifest, and scan for leaks/secrets
make check       # typecheck, lint, test
make ci          # typecheck, lint, coverage gate, pack/secret scan

License

MIT. See LICENSE. Security policy: SECURITY.md.