Mail Composer. Three lines to draft an email.
Compose, draft, and send emails from markdown files via the Gmail API. Wraps gmailr to eliminate the 80 lines of boilerplate that every email script repeats.
pak::pak("NewGraphEnvironment/mc")Set your default sender address in ~/.Rprofile:
options(mc.from = "you@example.com")Or via environment variable in ~/.Renviron:
MC_FROM=you@example.com
Then authenticate once per machine:
mc_auth()Write your email body in markdown. Everything above the --- separator is
a human-readable envelope (notes for the author) and is stripped by
mc_md_render() before conversion. Recipients, subject, and other
envelope fields are set as R parameters in mc_send().
# Email to Brandon - Cottonwood
**Subject:** Cottonwood plugs - 2026 planting
**To:** brandon@example.com
---
Hi Brandon,
Quick question about the cottonwood plugs.
Thanks,
AlSend it in three lines:
library(mc)
mc_send("communications/draft.md",
to = "brandon@example.com",
subject = "Cottonwood plugs - 2026 planting")That creates a Gmail draft with HTML formatting and the standard signature
appended. Authentication happens automatically via cached OAuth tokens
(run mc_auth() once per machine to set up).
When you're ready to send for real:
mc_send("communications/draft.md",
to = "brandon@example.com",
subject = "Cottonwood plugs",
draft = FALSE)Need an R-generated table in your email? Use mc_compose() to mix markdown
files, HTML, and kable/kableExtra objects:
df <- data.frame(Site = c("Nechako", "Mackenzie"), Plugs = c(4000, 3000))
body <- mc_compose(
"communications/intro.md",
knitr::kable(df, format = "html"),
"<p>Let me know if this looks right.</p>"
)
mc_send(html = body, to = "brandon@example.com", subject = "Planting plan")Large tables? Wrap in mc_scroll() for horizontal/vertical scrolling:
mc_compose(
"<p>Full dataset:</p>",
mc_scroll(knitr::kable(big_df, format = "html"), direction = "both")
)See vignette("tables-in-emails") for kableExtra styling and scrolling examples.
Full function documentation with examples: newgraphenvironment.github.io/mc
Reply into an existing conversation:
# Find the thread
mc_thread_find("from:brandon subject:cottonwood")
# Read the conversation to review context
mc_thread_read("19c05f0a98188c91")
# Include drafts in the thread (adds a status column: "sent" / "draft")
mc_thread_read("19c05f0a98188c91", drafts = TRUE)
# Send into it
mc_send("draft.md",
to = "brandon@example.com",
subject = "Re: Cottonwood plugs",
thread_id = "19c05f0a98188c91",
draft = FALSE)Note: gm_create_draft() does not support thread_id. Drafts are always
standalone. Use draft = FALSE to send directly into a thread, or send the
draft manually from the Gmail UI (Gmail will match by subject line if it
starts with "Re:").
Send an email later with send_at — either minutes from now or a specific time:
# Send in 10 minutes
proc <- mc_send("draft.md",
to = "brandon@example.com",
subject = "Cottonwood plugs",
send_at = 10)
# Send at a specific time
mc_send("draft.md",
to = "brandon@example.com",
subject = "Cottonwood plugs",
send_at = as.POSIXct("2026-02-24 09:11:00"))
# Check status or cancel
proc$is_alive()
proc$kill()On macOS, caffeinate keeps the machine awake until the email sends. The
laptop lid can be closed as long as power is connected. If the machine
sleeps through the send window (e.g., power loss), a 5-minute grace period
applies — past that, the send is skipped to prevent stale emails.
Outcomes are logged to ~/.mc/send_log.txt and trigger a macOS desktop
notification on success, skip, or failure.
Send to yourself to preview:
mc_send("draft.md",
to = "brandon@example.com",
subject = "Cottonwood plugs",
test = TRUE)Test mode redirects to your own address, strips CC/BCC, and ignores thread_id.
MIT
