Skip to content

Commit

Permalink
🐛 Fix TemplateRegistrationFailed error when sending email
Browse files Browse the repository at this point in the history
Should resolve #549, and added supporting documentation
  • Loading branch information
aaronleopold committed Jan 6, 2025
1 parent 6d7759d commit ea70c9c
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 9 deletions.
4 changes: 3 additions & 1 deletion crates/email/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ mod template;

pub use emailer::{AttachmentPayload, EmailerClient, EmailerClientConfig};
pub use error::{EmailError, EmailResult};
pub use template::{render_template, EmailTemplate};
pub use template::{
render_template, EmailTemplate, ATTACHMENT_TEMPLATE, BASE_TEMPLATE, TEMPLATES,
};

pub use lettre::message::header::ContentType as EmailContentType;
35 changes: 29 additions & 6 deletions crates/email/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ use std::path::PathBuf;
use crate::EmailResult;
use handlebars::Handlebars;

pub static BASE_TEMPLATE: &str = include_str!("../templates/base.hbs");
pub static ATTACHMENT_TEMPLATE: &str = include_str!("../templates/attachment.hbs");

pub static TEMPLATES: &[(&str, &str)] =
&[("base", BASE_TEMPLATE), ("attachment", ATTACHMENT_TEMPLATE)];

// TODO: expose this enumeration to the public API somehow, so that users can define their own template overrides

pub enum EmailTemplate {
Expand All @@ -19,6 +25,7 @@ impl AsRef<str> for EmailTemplate {
}

/// Render a template to a string using the given data and templates directory.
/// If the template does not exist on disk, the default template will be used.
///
/// # Example
/// ```no_run
Expand All @@ -40,15 +47,19 @@ pub fn render_template(
) -> EmailResult<String> {
let mut handlebars = Handlebars::new();
handlebars.register_partial("base_partial", "{{> base}}")?;
handlebars.register_template_file("base", templates_dir.join("base.hbs"))?;
handlebars
.register_template_file("attachment", templates_dir.join("attachment.hbs"))?;

for (name, template) in TEMPLATES {
let override_template = templates_dir.join(format!("{}.hbs", name));
if override_template.exists() {
handlebars.register_template_file(name, override_template)?;
} else {
handlebars.register_template_string(name, template)?;
}
}

Ok(handlebars.render(template.as_ref(), data)?)
}

// TODO: Write meaningful tests

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -58,7 +69,7 @@ mod tests {
}

#[test]
fn render_template_attachment() {
fn render_template_attachment_from_disk() {
let data = serde_json::json!({
"title": "Stump Attachment",
});
Expand All @@ -69,4 +80,16 @@ mod tests {

assert!(rendered.contains("Stump Attachment"));
}

#[test]
fn render_default_template_attachment() {
let data = serde_json::json!({
"title": "Stump Attachment",
});

let rendered =
render_template(EmailTemplate::Attachment, &data, PathBuf::new()).unwrap();

assert!(rendered.contains("Stump Attachment"));
}
}
1 change: 1 addition & 0 deletions docs/pages/guides/basics/_meta.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meta } from 'nextra'

export default {
webapp: 'App',
books: 'Books',
progress: 'Book Progress',
readers: 'Book Readers',
Expand Down
11 changes: 11 additions & 0 deletions docs/pages/guides/basics/webapp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Web App

The web app is the default, browser-based interface for Stump. It is a single-page application (SPA) built with [React](https://react.dev/).

## Access

The web app is accessible by navigating the host machine's IP address and the configured port in a web browser. For example, if your Stump server is running on your local machine and the default port is used, you would navigate to `http://localhost:10801` in your browser. If you are attempting to access the web app from a different machine on the same network, you would navigate to `http://{machine_ip}:10801`.

Accessing the web app from a different machine on a different network is a bit more complicated, and a bit beyond the scope of this guide. In the future, I'd love to have some curated tutorials for common setups (e.g., using a reverse proxy, setting up a VPN, etc.). I personally use Tailscale and Caddy, but there are many ways to accomplish this.

If you have a specific setup you'd like to see a guide for, or are willing to share your setup with a proper how-to write-up, please reach out!
8 changes: 8 additions & 0 deletions docs/pages/guides/configuration/server-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ The version of the Stump API to use. This should really be left alone and **not*

This corresponds to the `api_version` configuration option in the `Stump.toml` file.

### EMAIL_TEMPLATES_DIR

The directory where Stump will look for email templates. This is only required if you want to use custom email templates. By default, Stump will look for email templates in the `templates` directory in the root of the configuration directory. E.g., if your configuration directory is `~/.stump`, Stump will look for email templates in `~/.stump/templates`.

| Type | Default Value |
| ------ | ------------------------ |
| String | `{config_dir}/templates` |

#### PDFIUM_PATH

The path to the PDFium binary. This is only required if you want PDF support and you're running Stump outside of Docker, since the PDFium binary is included in the Docker image. You'll want to find and download the PDFium binary for your platform from [here](https://github.com/bblanchon/pdfium-binaries/releases), and then set this environment variable to the path of the binary.
Expand Down
6 changes: 4 additions & 2 deletions docs/pages/guides/desktop/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { Callout } from 'nextra/components'
# Desktop App

<Callout emoji="🚧">
This app is not yet available for download. I expect an initial **unstable** release to be
available towards the end of 2024. Expect bugs and missing features, but stay tuned for updates!
At the time of writing, the desktop app has a **nightly** release available on
[GitHub](https://github.com/stumpapp/stump/releases/tag/untagged-dcf8ed03bb6a19b142e0). It is not
stable, will contain bugs, and not feature complete. However if you decide to try it, please
report any issues you encounter!
</Callout>

The very same app you use in your browser, but with added features:
Expand Down
1 change: 1 addition & 0 deletions docs/pages/guides/features/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Meta } from 'nextra'
export default {
'api-keys': 'API Keys',
'book-clubs': 'Book Clubs',
email: 'Email',
'file-explorer': 'File Explorer',
upload: 'File Uploads',
'smart-list': 'Smart Lists',
Expand Down
95 changes: 95 additions & 0 deletions docs/pages/guides/features/email.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Callout } from 'nextra/components'

# Emailing

<Callout emoji="🔐">
Sending emails is gated behind the `email:send` user permission at a minumum. To learn more about
permissions, see the [permissions](/guides/access-control/permissions) guide.
</Callout>

Stump has basic email functionality built-in, allowing you to send books via email. This feature was primarily developed to support email-to-ereader integrations, however there are loose plans for expanding it in the future.

## Emailers

An emailer is essentially just a named configuration object that tells Stump how to send emails from a specific email account. Stump doesn't have its own email server or client, so it relies on SMTP to send emails. This means that you'll need to provide the necessary connection and auth details to send emails.

### Configuration

The following fields are required to configure an emailer:

- `name` - The name of the emailer, used for display purposes throughout the app
- SMTP-specifics:
- `host` - The SMTP server hostname, e.g., `smtp.gmail.com`
- `port` - The SMTP server port, e.g., `587`
- `username` - The username for the SMTP server, e.g., an email address
- `password` - The password for the SMTP server, encrypted at rest
- `TLS enabled` - Whether to use a secure connection (e.g., `true` for SSL, `false` for no security)
- Sender information:
- `Display name` - The name that will be displayed to recipients
- `Email address` - The email address that will be displayed to recipients

<Callout emoji="🔒">
The password field is encrypted at rest and not visible after creation. Stump will decrypt the
password when sending emails. This is a security measure to prevent unauthorized access to your
email server. You should still take care to secure your Stump instance.
</Callout>

You may also optionally configure the following fields:

- Max attachment size - The maximum size of attachments that can be sent via this emailer

## Device Aliases

A device alias refers to a named email address that can be quickly selected when sending books via email. The name is perhaps misleading, since your email doesn't necessarily have to be associated with a device. This is subject to change.

### Forbidden Aliases

A device alias may also be "inverted" to create a forbidden email address. This is useful for blocking certain email addresses from receiving books via your server. This really only comes into play when considering arbitrary send permissions, since without that permission a user would have to select from a list of device aliases which _aren't_ forbidden.

## Templates

Stump uses [handlebars](https://handlebarsjs.com/) for email templating. The default templates are very basic, but you can override them with your own custom templates. The only requirement is that you ensure the template fields align with the fields Stump expects.

The default templates can be found [on GitHub](https://github.com/stumpapp/stump/tree/main/crates/email/templates).

### Template Overrides

To override a template, simply create a new template with the same name in the template directory of your Stump instance. Stump will automatically use your custom template in place of the default.

The template directory defaults to a `templates` directory in the root of your Stump configuration directory. You can change this by setting the `EMAIL_TEMPLATES_DIR` environment variable. For more information on configuring Stump, see the [configuration](/guides/configuration/server-options) guide.

## Permissions

The following table lists perissions which can be assigned to users to control access to email functionality:

| Permission | Description | Notes / Inherited Permissions |
| ---------------- | ---------------------------------------------- | ----------------------------- |
| `emailer:read` | Allows the user to view registered emailers | |
| `emailer:create` | Allows the user to create new emailers | `emailer:read` |
| `emailer:manage` | Allows the user to manage existing emailers | `emailer:read` |
| `email:send` | Allows the user to send emails from the server | `emailer:read` |

## Audit Logs

Stump will log each email sent, however the logs are not currently exposed in the UI. This is a planned feature, but for now you can view the logs directly in the database with your preferred SQL client.

The following fields are logged for each record:

- The ID of the emailer used
- The email address the email was sent to
- A JSON blob containing metadata for the attachment
- The time the email was sent
- The user ID of the user who sent the email

## Future Plans

The email feature is not fully fleshed out yet, and there are plans to expand it in the future. Some of the planned features revolve around:

- Server invitations via email, e.g., inviting users to join your server
- Email notifications for various server events
- Book club email notifications

<Callout emoji="🚀">
If you have any ideas for expanding the email functionalities, feel free to open an issue on
[GitHub](https://github.com/stumpapp/stump/issues)
</Callout>

0 comments on commit ea70c9c

Please sign in to comment.