rguides

Sending Emails from R with blastula: Complete Guide

Introduction

The blastula package provides a simple interface for sending emails from R. It supports SMTP configuration, HTML and plain text messages, inline images, and file attachments. Unlike calling a system command or using a separate email client, blastula keeps the entire workflow inside R: you compose the message as an R object, preview it in a browser, and send it programmatically from scripts, R Markdown documents, or scheduled pipelines. This guide covers the essentials for integrating email functionality into your R workflows, from basic setup to production-ready sending with error handling.

Installation

Install blastula from CRAN:

install.packages("blastula")

After installation, load blastula into your R session. The package exports a small set of functions centered around compose_email() for building messages and smtp_send() for delivery. All email composition happens in R objects before anything touches the network, so you can iterate on templates without sending anything.

library(blastula)

Configuring SMTP

Before you can send anything, blastula needs SMTP credentials to authenticate with your email provider. The package avoids storing passwords in plain text by reading them from environment variables or encrypted files at send time. This separation of credentials from code means you can share scripts without exposing secrets.

# Using environment variables
smtp_creds <- creds_envvars(
  user = Sys.getenv("SMTP_USER"),
  password = Sys.getenv("SMTP_PASSWORD"),
  host = Sys.getenv("SMTP_HOST"),
  port = as.integer(Sys.getenv("SMTP_PORT"))
)

# Using a credentials file (editable file in home directory)
smtp_creds <- creds_file("~/.Renviron")
# # Edit the file to contain:
# SMTP_USER=your-email@example.com
# SMTP_PASSWORD=your-password
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587

Common SMTP settings for popular providers:

  • Gmail: host = “smtp.gmail.com”, port = 587
  • Outlook: host = “smtp.office365.com”, port = 587
  • Amazon SES: host = “email-smtp.us-east-1.amazonaws.com”, port = 587

Composing emails

The compose_email() function creates an email object. You can specify plain text, HTML content, or both.

# Plain text email
email_text <- compose_email(
  body = "Hello, this is a plain text message."
)

# HTML email
email_html <- compose_email(
  body = md("
# Hello

This is an **HTML** message with formatted content.

- Point one
- Point two
  ")
)

# Email with both plain text and HTML
email_both <- compose_email(
  body = "Hello, this is the plain text version.",
  body_html = md("Hello, this is the **HTML** version.")
)

The md() function marks content as markdown, which blastula converts to HTML for the email body. You can use headers, bold, italics, lists, and links with standard markdown syntax. For multi-part emails, supplying both body (plain text) and body_html ensures recipients see formatted content when their client supports HTML and fall back to the plain text version otherwise. Once the basic message is composed, add_attachment() lets you include files alongside the body content.

Adding attachments

Use the add_attachment() function to attach files:

# Create email with attachment
email_attachment <- compose_email(
  body = "Please find the attached report."
) %>%
  add_attachment(
    file = "report.pdf",
    mime_type = "application/pdf"
  )

# Or use a path
email_attachment <- compose_email(
  body = "Attached is the data file."
) %>%
  add_attachment(
    file = "/path/to/data.csv",
    mime_type = "text/csv"
  )

Attachments are sent as MIME parts alongside the email body, with the mime_type argument telling the recipient’s client how to handle the file. For common formats like PDF, CSV, or PNG, the MIME type determines whether the file opens inline or downloads. Multiple attachments can be chained with additional add_attachment() calls, and each one gets its own filename in the recipient’s email client.

Adding inline images

Embed images directly in HTML emails using add_image():

email_image <- compose_email(
  body = md("
# Report

![Chart](cid:chart-image)

See the chart above for details.
  ")
) %>%
  add_image(
    file = "chart.png",
    image_id = "chart-image"
  )

The image_id must match the content ID in your markdown (cid:chart-image). Inline images are encoded as base64 data URIs inside the HTML, so recipients see them without downloading attachments or relying on external hosting. For ggplot2 charts, use add_image(plot = ggplot_obj) to render the plot as a temporary PNG and embed it automatically. Once your email template is ready, you can send it to a list of recipients by iterating over a data frame and composing a personalized message for each row.

Sending to multiple recipients in a loop

For sending personalized emails to a list of recipients:

# Data frame with recipient information
recipients <- data.frame(
  name = c("Alice", "Bob", "Charlie"),
  email = c("alice@example.com", "bob@example.com", "charlie@example.com"),
  amount = c(100, 250, 75)
)

# Send personalized emails
for (i in seq_len(nrow(recipients))) {
  email <- compose_email(
    body = md(paste0(
      "Dear ", recipients$name[i], ",\n\n",
      "Your account balance is $", recipients$amount[i], ".\n\n",
      "Thank you for your business."
    ))
  )
  
  smtp_send(
    email = email,
    from = "noreply@example.com",
    to = recipients$email[i],
    subject = paste0("Account Update - ", recipients$name[i]),
    credentials = smtp_creds
  )
  
  # Add delay to avoid rate limiting
  Sys.sleep(1)
}

The loop iterates through each row of the recipients data frame, composing a unique email and sending it individually. The Sys.sleep(1) call adds a one-second delay between sends to avoid tripping rate limits on your SMTP provider. For bulk campaigns, store send status in the data frame so interrupted runs can resume from where they stopped. Always wrap send operations in error handling so a single failed delivery doesn’t crash the entire loop.

Error handling

Wrap sending in tryCatch for safe error handling:

send_email_safely <- function(email, from, to, subject, creds) {
  tryCatch(
    {
      smtp_send(
        email = email,
        from = from,
        to = to,
        subject = subject,
        credentials = creds
      )
      message("Email sent successfully to ", to)
      return(TRUE)
    },
    error = function(e) {
      warning("Failed to send email to ", to, ": ", e$message)
      return(FALSE)
    }
  )
}

The tryCatch wrapper catches any error during the SMTP transaction and returns FALSE instead of crashing the script. The calling code can check the return value to track which recipients received the email and which failed. For production pipelines, log both successes and failures so you can retry failed sends without duplicating messages to recipients who already received them.

Testing configuration

Use test_smtp() to verify your SMTP configuration works:

test_smtp(
  creds = smtp_creds,
  from = "test@example.com",
  to = "test@example.com"
)

This sends a test email through your configured server. Always verify your SMTP settings with a test send before deploying to production. A failed test usually indicates a wrong host, port, or authentication method — check that the port matches the encryption setting your provider expects.

Security considerations

  • Store SMTP credentials in environment variables or a secure credentials file rather than in scripts. Use the keyring package or blastula’s create_smtp_creds_key() to store passwords in the system keychain.
  • Never hardcode passwords in scripts that may be shared or committed to version control.
  • Use application-specific passwords if your email provider supports them; for Gmail with two-factor authentication enabled, generate an App Password rather than using your account password.
  • Consider using two-factor authentication with app passwords for any automated email account.
  • For production use, restrict SMTP access to specific IP addresses when possible to limit exposure if credentials are compromised.

Testing without sending

During development, preview_email(email) opens the email in the default browser for visual inspection without sending. This avoids accidentally emailing test content to real recipients. smtp_send() has a dryrun = TRUE argument that validates the connection and message without delivering; use it to test SMTP configuration before enabling live sends. Store test addresses in an environment variable to easily switch between test and production recipients.

Sending via external services

For production bulk email, SMTP from Gmail or Outlook has rate limits and deliverability issues. Services like SendGrid, Mailgun, and Amazon SES provide better deliverability, tracking, and higher volume limits. blastula integrates with SendGrid via smtp_send() with host = "smtp.sendgrid.net", port = 587, and your SendGrid API key as the password. The same creds_envvar() pattern keeps the API key out of code. For simple text emails without HTML styling, curl::send_mail() uses libcurl directly and has less overhead than blastula.

Conditional and threshold-based alerts

Automated reports that alert only when conditions are met, such as an anomaly detected or a threshold exceeded, require conditional email sending. The condition check happens in R before the email is composed. If the condition is not met, the script exits without sending. If it is met, the email includes the specific values that triggered the alert. For monitoring pipelines, wrap the data fetch in tryCatch and send an error alert on failure so that the monitoring itself is monitored rather than failing silently when the data source is unavailable.

See also