WP Manifestindependent plugin directory
manifest / content / send-to-e-reader

Send to E-Reader WP Registry grade D+. High-severity findings · 4 findingsWP RegistryHigh-severity findings · 4 findingsOpen the reportD+

Send new articles directly to your e-reader via e-mail or download the ePub.

★ 6stars
293release downloads
1.0.0latest · 9mo ago

Install

Download send-to-e-reader.zip From the 1.0.0 release on GitHub, Jan 23, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/akirk/send-to-e-reader/releases/download/1.0.0/send-to-e-reader.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Send posts to your e-reader as ePub files, by email or direct download. Works standalone or with the Friends plugin.

Description

Try it in WordPress Playground · Try it with demo data, which comes with a few posts and two e-readers already configured.

Send to E-Reader turns one or more WordPress posts into an ePub file. You can download that file straight away, or have it mailed to the address your e-reader listens on, so the article shows up on the device without a cable.

See the post Subscribing to RSS Feeds on your E-Reader using your own WordPress blog for more details on how it works.

Where you use it

  • On the Posts screen, a Send to E-Reader row action sends a single post. With several e-readers configured, each one gets its own entry.
  • Select several posts and pick Send to E-Reader from the Bulk actions menu to bundle them into one multi-chapter ePub.
  • With the Friends plugin installed, the same actions appear on the posts of the feeds you follow, so you can move a batch of unread articles onto your e-reader in one go. The plugin remembers which posts it has already sent, so you can send just the new ones.
  • With the Post Collection plugin installed, the same actions appear in its app: each collected article can be downloaded as ePub or sent to a device, and several can be ticked off and bundled into one multi-chapter file. There is deliberately no button for the whole backlog — a read-it-later archive runs into the thousands of unread articles, and every image in every chapter is fetched while the file is built, so ticking off what you want is what says how much work to do.
  • If the AI Assistant plugin is active, conversations can be exported as ePub through its export menu.

Delivery methods

You configure any number of e-readers on the plugin's E-Readers screen. Each one is one of:

  • ePub via E-Mail — a generic e-reader that accepts documents at an email address.
  • Kindle — sends to your Send-to-Kindle address (@free.kindle.com). Amazon accepts ePub directly, so no conversion is needed.
  • PocketBook — sends to your PocketBook Cloud address (@pbsync.com).
  • Download ePub — no email at all; the file is offered as a download in the browser.

Mail is sent through WordPress's own wp_mail(), so whatever SMTP setup the site already has is what gets used. The configured e-readers are stored site-wide: everyone who can reach the E-Readers screen works from the same list of devices. If more than one e-reader is active, the Posts screen offers one row action and one bulk action per device, so you pick where a post goes.

The ePub it builds

The ePub is generated on the fly from the post content: a title page, one chapter per post, and the post's byline and permalink. Images in the post body are embedded into the file, including images hosted on the same site, so the article still reads correctly offline. Content is converted to XHTML so the e-reader's renderer accepts it.

ePubs can also be fetched over a special download URL: append a password-derived query parameter to any page of your site and it returns an ePub of all posts, the new ones, the last one, or a list to pick from. Some e-readers can open a URL directly, which makes this the fastest route onto the device. On a Post Collection app URL the same parameter takes unread as well, so the e-reader can pull down the articles you have not read yet, for one collection or across all of them. Any selection can carry a number — unread-10 takes the ten most recent — which is what to reach for when the backlog is long enough that one file would take a while. The URLs are listed on the Settings screen.

Standalone, the plugin adds its screens under Tools → Send to E-Reader (and Settings → Send to E-Reader). With the Friends plugin active, they move under the Friends menu instead.

Installation

  1. Upload the plugin files to /wp-content/plugins/send-to-e-reader, or install the plugin through the WordPress Plugins screen.
  2. Activate the plugin through the Plugins screen in WordPress.
  3. Go to Tools → E-Readers and add an e-reader: give it a name and, for the email-based types, the address your device receives documents on.
  4. Open the Posts screen and use the Send to E-Reader row action, or select several posts and use the bulk action.

The plugin has no required dependencies. The Friends plugin is optional and unlocks sending posts from the feeds you follow.

Frequently Asked Questions

Do I need the Friends plugin?

No. The plugin works standalone on any WordPress site and adds its own settings screens. If the Friends plugin happens to be active, the two integrate and the e-reader actions also appear on your feed posts.

Which devices are supported?

Anything that can receive a document by email, plus a plain download for everything else. There are dedicated presets for Kindle and PocketBook; the generic ePub via E-Mail reader covers any other device with a send-to-device address.

Why ePub and not MOBI?

Amazon accepts ePub by mail, so MOBI support was removed. Every supported device reads ePub.

The email never arrives. What should I check?

The plugin uses WordPress's wp_mail(). If other mail from your site does not arrive either, the problem is the site's mail configuration, not this plugin — an SMTP plugin usually fixes it. Also check that your device's service allows mail from your site's sending address; Kindle in particular only accepts mail from addresses on its approved-senders list.

Can I send several posts as one file?

Yes. Select them on the Posts screen and use the Send to E-Reader bulk action; they become one ePub with a chapter per post.

Where are my e-reader addresses stored?

In a site option, not per user. The E-Readers screen requires the edit_private_posts capability, so editors and administrators share one list of devices. Sending a post additionally requires that you are allowed to read that post.

Changelog

Unreleased

  • Add e-reader actions to the Post Collection app: per-article and multi-select sending, a dialog for the unread articles, and download URLs that can carry how many articles to take

1.1.0

  • Fix saving the E-Readers screen deleting every configured e-reader
  • Keep e-reader ids stable so delivery settings survive an edit
  • Pick which e-reader to send to when several are configured
  • Only send posts the current user is allowed to read
  • Embed same-site images locally in the generated ePub
  • Export AI Assistant conversations as ePub
  • Reuse Static Archive HTML for ePub exports
  • Manage article notes through the Post Collection plugin
  • Clearer admin messaging when running standalone

1.0.0

  • Plugin now works fully standalone without requiring the Friends plugin
  • Renamed plugin from "Friends Send to E-Reader" to "Send to E-Reader"
  • Added standalone settings pages and admin UI
  • Added test suite

0.8.4

  • Allow creating books via bulk edit ([#13])
  • Fixed a bug where a non-existant image could cause the rest of the document to be a link
  • Enable the URL GET parameter on any page
  • One more fix for empty titles in posts

0.8.3

  • Try harder to ensure the title is not empty (#12)

0.8.2

  • Ensure the title is not empty (#11)
  • Improve the Reading Summary function (#9)

0.8.1

  • Add a Download URL previewer (#7)
  • Add the ability to mark an article as new (#6)

0.8.0

  • Fix choking on invalid SVGs
  • Enable unsent posts for any author
  • Add the ability to download ePub through special URLs (#5)

0.7

  • Fix multi-item dialog not popping up.

0.6

  • Remove MOBI support since Amazon now accepts EPubs by mail.
  • Introduce Reading Summaries: You can create a new draft posts from your sent articles so that you can easily post about them.
  • Remember which posts were already sent, enabling a "Send x new posts to your e-reader" button in the header.

0.5

  • Remember which posts were sent and allow sending just the new ones. [WIP display works, actual sending not yet]
  • Automatically send new posts every week. [WIP setting screen is there, saving setting and cron not yet]
  • Allow auto-creating of "reading summary" draft posts with link plus excerpt and room for your own comments.
  • New-style setting screen with separate screen for reading summaries.

0.4

  • Update for Friends 2.0

0.3

  • Allow downloading the ePub.
  • Theoretically add support for Tolino. Not functional because Thalia doesn't want to provide OAuth2 credentials.

Releases

10 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
1.0.0 latest
Jan 23, 2026 9mo ago
send-to-e-reader.zip
293
Dec 19, 2024 1 year ago
tag only
—
Nov 29, 2024 1 year ago
tag only
—
Nov 24, 2024 1 year ago
tag only
—
Aug 4, 2023 3 years ago
tag only
—
Aug 3, 2023 3 years ago
tag only
—
0.7
Jan 4, 2023 3 years ago
tag only
—
0.6
Nov 13, 2022 3 years ago
tag only
—
0.5
Apr 9, 2022 4 years ago
tag only
—
0.3
Nov 11, 2021 4 years ago
tag only
—
D+ grade

Security

Send to E-Reader 1.0.0 · audited by WP Registry

High-severity findings.

4 high
Audited release
1.0.0
Findings
4
Worst severity
high
Content hash
34f547ea25e317f6d153eee3…

Findings

  • high Authenticated (Contributor+) missing authorization to export arbitrary posts as a public ePub or e-mail

    missing_capability

    handle_bulk_actions() and ajax_send() package attacker-chosen post IDs with get_post() and never call current_user_can('read_post') / current_user_can('edit_post'). Activation (activate_plugin) inserts an active E_Reader_Download. send_posts() then wp_handle_sideload()s the generated ePub into the public uploads directory and returns its URL; handle_bulk_actions wp_safe_redirect()s the attacker there. A Contributor who can load edit.php obtains a bulk-posts nonce and can POST action=send-to-e-reader&post[]={id} for any ID (WordPress core only capability-checks built-in bulk actions; custom actions fall through to the plugin filter with the raw ID list). The same IDs work via wp_ajax_send-post-to-e-reader once a send-post-to-e-reader nonce is available (localized on the Friends frontend for every edit_private_posts user, i.e. Author+). ajax_unmark() likewise deletes sent-to-ereader meta for any post after only a nonce check. Settings pages (configure_ereaders / settings) are registered with cap edit_private_posts, so Author+ can also rewrite Kindle destination addresses and the download password. Subscriber/customer never get the AJAX nonce and cannot load edit.php, so this is not a customer-role issue; blast radius is still any unpublished/private/Friends-cached post turned into a world-readable /wp-content/uploads/*.epub or an e-mail attachment to the configured reader.

    includes/class-send-to-e-reader.php:201-202,538-601,1104-1156,1182-1194

    Recommendation

    Require current_user_can('edit_post', $post_id) (or a dedicated cap such as edit_private_posts) inside ajax_send, ajax_unmark, and handle_bulk_actions before get_post/send_posts. Do not sideload ePubs into publicly reachable uploads; stream the file or store it under a non-web path with an authenticated download. Gate configure_ereaders/settings on manage_options. Refuse to send posts the current user cannot read.

  • high Unauthenticated ePub export and private-post disclosure via CRC32 download-password GET key

    auth_bypass

    download_via_url() is hooked to template_include unconditionally. enable_download_via_url() treats the presence of GET parameter name 'epub' concatenated with option send_to_e_reader_download_password as authentication. The default password is hash('crc32', wp_salt('nonce')) — 8 hex characters (32-bit search space) — and is not written on activation, so every unsaved install uses that CRC32. Valid values all/last/new/list or an array of post IDs (the list template posts checkboxes named epub{password}[]) cause the handler to WP_Query the current page (get_query_vars() forces post_type=any), generate an ePub, wp_handle_sideload it to public uploads, update_post_meta sent-to-ereader, always call create_reading_summary() (wp_insert_post/wp_update_post of a draft), and wp_redirect to the public file URL. When the Friends plugin is active, the same GET key is hooked to friends_friend_posts_query_viewable; Friends then sets the /friends/ query viewable and post_status to publish+private+future, so an unauthenticated client who knows (or brute-forces) the 32-bit token dumps private friend-cache posts. The token travels as a query-parameter name (logs, Referer, e-reader history). This is the intended e-reader bookmark URL, but the default token is too small, there is no rate limit, and private content is copied to a guessable public uploads path.

    includes/class-send-to-e-reader.php:203,749-778,949-1101

    Recommendation

    Do not treat a 32-bit CRC32 as a capability token: generate a long random secret (wp_generate_password 32+), store it hashed, and require it as a value not a parameter name. Do not flip friends_friend_posts_query_viewable for unauthenticated requests without an additional signed, single-use link. Never wp_handle_sideload private content into publicly listed uploads; stream the ePub. Do not wp_insert_post or stamp sent-to-ereader from an unauthenticated request. Rate-limit guesses of the parameter.

  • high SSRF: ePub generation fetches arbitrary http(s)/ftp URLs from post <img> via file_get_contents

    ssrf

    E_Reader::generate_file() calls PHPePub addChapter(..., EPub::EXTERNAL_REF_ADD). processChapterImages → resolveImage → ImageHelper::getImage → FileHelper::getFileContents($source). FileHelper hardcodes $isExternal=false, so the curl branch never runs and file_get_contents($source) is used for every URL, including http://169.254.169.254/, http://127.0.0.1/, ftp://, and other hosts that wp_http_validate_url / wp_safe_remote_get would reject. allow_url_fopen (common default) is sufficient. A Contributor can place <img src="http://internal/"> in a post they are allowed to create and then trigger send via the bulk action or row-action link (see missing-capability finding); with Friends, a malicious subscribed feed is enough if the owner (or auto-send) generates an ePub. The HTTP request is issued even when the response is not a valid image (blind SSRF / port scan). Not unauthenticated on a default standalone install.

    includes/class-e-reader.php:231-233

    Recommendation

    Fetch remote images only through wp_safe_remote_get() (which blocks private/loopback/link-local addresses) or disable EXTERNAL_REF_ADD. If local files are required, resolve them under the uploads directory after realpath() and a prefix check. Do not call file_get_contents() on attacker-controlled URLs.

  • high Unauthenticated reflected XSS in bundled RelativePath.Example1.php

    xss_reflected

    The plugin zip ships vendor demo scripts under libs/ with no ABSPATH guard and no .htaccess. libs/grandt/relativepath/RelativePath.Example1.php is directly executable at /wp-content/plugins/send-to-e-reader/libs/grandt/relativepath/RelativePath.Example1.php. It reads $_POST['path'], echoes stripslashes($_POST['path']) into an HTML value attribute and again inside a <pre>, and also prints $_SERVER['DOCUMENT_ROOT']. There is no escaping. An unauthenticated attacker hosts an auto-submitting form that POSTs a payload such as "><script>…</script> to that URL; the script runs in the victim's origin. WordPress auth cookies are HttpOnly, but same-origin JS can fetch /wp-admin/, scrape nonces, and create an administrator. GET without POST already discloses the document root. Other shipped demos (libs/grandt/phpepub/test.php, libs/phpzip/phpzip/legacy/*.php, BinString.Example*.php, legacy/composer.phar) enable display_errors and/or fatal-require missing autoloaders (path disclosure) and should be removed with this file.

    libs/grandt/relativepath/RelativePath.Example1.php:4-7,80-95

    Recommendation

    Delete vendor example/test/legacy PHP (RelativePath.Example1.php, BinString.Example*.php, phpepub/test.php, phpepub/legacy/*, phpzip/legacy/*, composer.phar) from the distributed zip. Do not ship files that run outside WordPress. If examples must remain, deny web access (.htaccess/nginx) and wrap every PHP file with defined('ABSPATH') || exit.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.