This vignette describes how roreviewapi sends
editor-search emails through the Gmail API, sending “from” the
reviewbot@ropensci.org alias but authorized under a single
Google account. Set that account once as an environment variable, and
every later step refers back to it:
This vignette complements the admin setup vignette, covering only the Gmail-specific pieces of the editor volunteer search workflow.
Prerequisites
- A Google Cloud Console project owned by the rOpenSci Workspace’s Cloud Identity org
- A local machine with a browser, for the one-time OAuth authorization
- SSH access to the Digital Ocean droplet
1. Google Cloud Console
In the ro-auto-email project:
- Enable the Gmail API.
- Configure the OAuth consent screen with
Audience: Internal (requires the project to be owned by
the Workspace’s Cloud Identity org). Internal governs who can
authorize the app, not who receives mail — it skips Google
verification and the 7-day refresh-token expiry that applies to
unverified External apps. Request scope
https://www.googleapis.com/auth/gmail.send. - Create an OAuth 2.0 Client ID, type Desktop
app. Download
client_secret_<id>.apps.googleusercontent.com.json.
2. Authorize once, locally
Run this on a local machine, not the droplet (it needs a browser and
a localhost redirect):
library (gmailr)
gm_auth_configure (path = "client_secret_<id>.apps.googleusercontent.com.json")
gm_auth (
email = Sys.getenv ("USER_EMAIL"),
scopes = c ("gmail.modify", "gmail.settings_basic"),
cache = "gmailr_token"
)Log in as $USER_EMAIL and grant consent. This caches a
refresh token to the gmailr_token/ directory, which is
portable to the droplet afterward since it is tied to the Google
account, not the machine.
Always pass the same scopes used here in any later
gm_auth() call — a mismatch forces a fresh interactive
re-authorization, which does not work headless on the droplet.
3. Send-as alias
Add reviewbot@ropensci.org as a “Send mail as” address
under $USER_EMAIL’s Gmail settings (Settings → Accounts →
Send mail as). No separate authorization is needed, as the
gmail.send scope already covers sending as any verified
alias of that account. This is set via gm_from() on each
outgoing message.
4. Code
-
gmail_auth()configures and authorizesgmailrfrom the cached credentials. -
gmail_send(to, subject, html_body)builds and sends one message viagm_mime()/gm_from()/gm_to()/gm_subject()/gm_html_body()/gm_send_message(). -
gmail_send_batch(emails, links, subject, repo, issue_id)loopsgmail_send()once per recipient withSys.sleep(0.1)between sends, since there is no Gmail equivalent of a batch-send endpoint for personalized messages.
The send_search and handle_click endpoints
each take an injectable sender parameter (default
gmail_send_batch / gmail_send) for
testing.
5. Environment variables
| Variable | Value |
|---|---|
GMAIL_SENDER |
reviewbot@ropensci.org |
GMAIL_OAUTH_CLIENT_SECRET |
/data/email/client_secret_<id>.apps.googleusercontent.com.json |
GMAIL_TOKEN_CACHE |
/data/email/gmailr_token |
GMAIL_AUTH_EMAIL |
$USER_EMAIL |
On the droplet, set these in the untracked .env file, as
described in the admin
setup vignette; use a local .env file for local runs.
The two file paths are inside /data/email/, the container
side of the bind-mounted data directory, so the files themselves go in
/srv/roreviewapi/data/ on the droplet (Section 6).
6. Deploy credentials to the droplet
Copy
client_secret_<id>.apps.googleusercontent.com.json
and the gmailr_token/ directory to
/srv/roreviewapi/data/ (bind-mounted into the container at
/data/email). That directory is Docker/root-owned, so
scp to your home directory first, then move into place:
scp client_secret_<id>.apps.googleusercontent.com.json youruser@droplet:~/
scp -r gmailr_token youruser@droplet:~/
# on the droplet:
sudo mv ~/client_secret_<id>.apps.googleusercontent.com.json /srv/roreviewapi/data/
sudo mv ~/gmailr_token /srv/roreviewapi/data/
sudo chmod 600 /srv/roreviewapi/data/client_secret_<id>.apps.googleusercontent.com.json
sudo chmod 700 /srv/roreviewapi/data/gmailr_token
sudo chmod 600 /srv/roreviewapi/data/gmailr_token/*7. Verify
- Call
send_search()against a real/test issue; confirm delivery fromreviewbot@ropensci.org. - Click a tracking link; confirm the notify-email arrives via
gmail_send(). - Run the test suite.
8. Transfer maintenance
This section covers the Gmail-specific parts of transferring maintenance. Access, tokens for other services, the cron job, and the general redeployment steps are in the admin setup vignette.
Operations only (a new person deploys/debugs, auth
stays as $USER_EMAIL) — nothing about the token changes.
The only Google step is to add them to the ro-auto-email
Google Cloud project’s IAM.
Authorizing identity itself (needed if
myname@ropensci.org becomes unusable, e.g. leaving the
org). The .env values affected are
GMAIL_AUTH_EMAIL (the new maintainer’s address) and the
contents of the GMAIL_TOKEN_CACHE directory (a new token).
GMAIL_SENDER stays reviewbot@ropensci.org, and
GMAIL_OAUTH_CLIENT_SECRET is unchanged as long as the same
Cloud project and OAuth client are kept.
- Add the new maintainer to the
ro-auto-emailGoogle Cloud project’s IAM, so they can download theclient_secret_....json. Because the consent screen audience is Internal, their account must belong to the rOpenSci Google Workspace; an account outside it cannot authorize the app. - Add
reviewbot@ropensci.orgas a “Send mail as” alias under the new maintainer’s Gmail account (Section 3). - They run the authorization in Section 2 as themselves (with
USER_EMAILset to their own address), using the sameclient_secret_....json, producing their owngmailr_token/. Thescopesmust be unchanged from those ingmail_auth(). - Deploy their
gmailr_token/(andclient_secret_....json, if it changed) to/srv/roreviewapi/data/, replacing the old ones (Section 6). Remove the oldgmailr_tokendirectory first, so that stale files do not remain. - Update
GMAIL_AUTH_EMAILin the droplet’s.envto their address. - Redeploy by running
restart.sh, then verify as in Section 7. - Revoke the old grant from the departing account’s Google Account → Security → “Third-party apps with account access”.
Editor search endpoints
Four endpoints support the volunteer editor search workflow. All
require the shared secret token.
GET /send_search
Fetches current editor email addresses from AirTable and GitHub, inserts a search record into the database, and dispatches personalised click-link emails via Gmail. The submission type (standard vs. stats) is determined automatically from the GitHub issue template.
Parameters: repourl, repo (org/repo),
issue_id, secret.
GET /click/<token>
Records a volunteer response. Returns an HTTP 200 confirmation page, an “already used” page on duplicate clicks, or an “expired” page if the search has been deactivated. Sends a notification email to the current editor-in-chief on the first valid click.
No authentication required — the token itself is the credential.
Notify email cache
At startup, serve_api() fetches the current
editor-in-chief email address from AirTable and writes it to
/data/email/notify_email.txt (alongside the SQLite
database). This cache is refreshed every 24 hours via a background
timer. If the AirTable call fails, the existing cached value is
preserved and an error is logged.
The cached address is used as the notification recipient whenever a volunteer clicks a search link.
