Skip to contents

This file provides guidance to AI development agents (e.g., Claude Code, GitHub Copilot) when working with this repository.

Repository Overview

artsend is a pure email sending integration package for the Artalytics platform, providing transactional email functionality via the Resend API. This package follows a clean architecture with zero database dependencies.

Purpose

  • Email sending service integration (Resend API)
  • Contact form email delivery
  • HTML email template generation
  • Pure functional API wrapper (no database, no app state)

Architecture Role

This is an API integration package in the Artalytics ecosystem: - No dependencies on artcore, artutils, or database packages - Pure email service - receives data, sends email - Reusable across any part of the platform

Core Philosophy

Clean Architecture Principles

  1. No Database Dependencies: Email addresses and data passed as parameters
  2. Pure Functions: No side effects beyond API calls
  3. Separation of Concerns: Just email sending, nothing else
  4. Easy Testing: Mock API calls, no database setup needed

What artsend Does NOT Do

  • ❌ Query databases
  • ❌ Manage user sessions
  • ❌ Handle Shiny reactivity
  • ❌ Know about artists/artworks/UUIDs
  • ❌ Store or cache data

Key Components

Email Sending (send_contact_email())

result <- artsend::send_contact_email(
  to = "artist@example.com",           # From caller's data
  visitor_name = "John Doe",
  visitor_email = "john@example.com",
  message = "I love your work!",
  artwork_title = "Sunset Dreams",      # Optional context
  artwork_url = "https://..."           # Optional context
)

Configuration (get_resend_config())

  • Reads ART_RESEND_KEY from environment
  • Validates API key format
  • Returns configuration list

Validation (validate_email_format())

  • Email format validation
  • Parameter validation
  • Input sanitization

HTML Templates (build_contact_email_html())

  • Professional responsive design
  • HTML escaping for security
  • Artwork context support
  • Mobile-optimized layout

Development Workflow

Setup

# Install dependencies
Rscript -e "pak::pkg_install('.')"

# Load for development
Rscript -e "devtools::load_all()"

# Set API key for testing
export ART_RESEND_KEY=re_xxxxxxxxxxxxx

Testing Strategy

# Unit tests (fast, no API calls)
devtools::test()

# Integration tests (requires API key)
Sys.setenv(ART_RESEND_KEY = "re_xxx")
devtools::test(filter = "integration")

# Check package
devtools::check()

Adding New Email Types

  1. Create Template Function in R/email-template.R:
build_notification_email_html <- function(user_name, notification_text) {
  # Build HTML template
}
  1. Create Sending Function in R/send-*.R:
send_notification_email <- function(to, user_name, notification_text) {
  # Validate, build, send
}
  1. Add Tests in tests/testthat/test-*.R:
test_that("send_notification_email validates inputs", {
  # Test validation
})
  1. Document with Roxygen:
#' @export
send_notification_email <- function(...) { }
  1. Regenerate Docs:
devtools::document()

Environment Configuration

Required Variables

# Production
ART_RESEND_KEY=re_xxxxxxxxxxxxx

# Testing (optional)
TEST_EMAIL_TO=test@example.com

Resend Dashboard Setup

  1. Sign up at resend.com
  2. Verify sending domain (e.g., contact.artalytics.app)
  3. Generate API key
  4. Configure sender address (e.g., )

Dependencies

Minimal Production Dependencies

Imports:
  httr2,      # Resend API HTTP requests
  jsonlite,   # JSON handling
  glue,       # String templating
  htmltools   # HTML escaping for security

Why These Dependencies?

  • httr2: Modern HTTP client for API requests
  • jsonlite: JSON encoding/decoding for API payloads
  • glue: Clean string interpolation for templates
  • htmltools: HTML escaping to prevent injection attacks

Test Dependencies

Suggests:
  testthat,   # Testing framework
  covr,       # Code coverage
  mockery,    # Mocking for unit tests
  withr       # Environment variable management in tests

Integration Patterns

In Shiny Applications (e.g., modGallery)

# In server logic
observeEvent(input$submit, {
  # Email already in appdata - no DB query needed!
  result <- artsend::send_contact_email(
    to = r$appdata$artist$info$email,
    visitor_name = input$name,
    visitor_email = input$email,
    message = input$message,
    artwork_title = r$appdata$artwork$info$full$title
  )

  if (result$success) {
    showNotification("Email sent!")
  } else {
    showNotification(paste("Error:", result$error), type = "error")
  }
})

In Batch Processing

# Send multiple emails
results <- lapply(recipients, function(recipient) {
  artsend::send_contact_email(
    to = recipient$email,
    visitor_name = recipient$name,
    # ...
  )
})

Error Handling Pattern

result <- artsend::send_contact_email(...)

if (!result$success) {
  # Log error
  logger::log_error("Email failed: {result$error}")

  # Notify user
  shiny::showNotification(
    "Failed to send email. Please try again.",
    type = "error"
  )

  # Could queue for retry, etc.
}

Testing Approach

Unit Tests (Fast, No API)

test_that("validates email format", {
  expect_true(validate_email_format("user@example.com"))
  expect_false(validate_email_format("invalid"))
})

Integration Tests (Require API Key)

test_that("sends email via Resend", {
  skip_on_ci()
  skip_if_not(is_resend_configured())

  result <- send_contact_email(...)
  expect_true(result$success)
})

Mocking for Unit Tests

test_that("handles API errors gracefully", {
  mockery::stub(
    send_contact_email,
    "httr2::req_perform",
    stop("API error")
  )

  result <- send_contact_email(...)
  expect_false(result$success)
})

Security Considerations

HTML Escaping

All user inputs are escaped using htmltools::htmlEscape() to prevent XSS:

visitor_name_safe <- htmltools::htmlEscape(visitor_name)
message_formatted <- htmltools::htmlEscape(message)

Email Validation

  • Format validation via regex
  • Required field validation
  • Type checking

API Key Security

  • Never commit API keys
  • Use environment variables only
  • Validate key format on load
  • Warn if key doesn’t match expected pattern

Common Development Tasks

Add New Email Type

  1. Create template function
  2. Create sending function
  3. Add validation
  4. Write tests
  5. Document
  6. Export

Update HTML Template

  1. Edit build_contact_email_html() in R/email-template.R
  2. Test rendering with sample data
  3. Check mobile responsiveness
  4. Verify HTML escaping

Debug Email Sending

# Enable verbose logging
options(httr2_verbose = TRUE)

# Test with debug output
result <- artsend::send_contact_email(...)
print(result)

Change API Provider

If switching from Resend to another service: 1. Update get_resend_config()get_email_config() 2. Update API endpoint in send_contact_email() 3. Update payload structure for new API 4. Update tests 5. Update environment variable names

Performance Considerations

Email Sending is Synchronous

  • Each call blocks until API responds
  • Consider async for batch operations
  • Add timeout handling for production

Rate Limiting

  • Resend has rate limits (check plan)
  • Consider implementing client-side rate limiting
  • Queue system for high-volume scenarios

Template Rendering

  • Templates are lightweight (pure string ops)
  • No caching needed for small volumes
  • Consider template caching for high volumes

Future Enhancements

Potential Features

Extensibility

Package is designed to easily add: - Different email types (notifications, newsletters, etc.) - Different providers (SendGrid, Mailgun, etc.) - Different templates (marketing, transactional, etc.)

Troubleshooting

Common Issues

Email not sending: - Check ART_RESEND_KEY is set - Verify API key is valid in Resend dashboard - Check sender domain is verified - Review Resend API status

Invalid email format errors: - Ensure all emails pass validation - Check for whitespace or special chars - Verify email parameters are non-empty

HTML rendering issues: - Test template with various email clients - Verify HTML escaping is working - Check for inline CSS issues

Debug Tools

# Check configuration
artsend::is_resend_configured()
artsend::get_resend_config()

# Validate inputs
artsend::validate_email_format("test@example.com")

# Test with verbose HTTP
options(httr2_verbose = TRUE)

This package provides pure email sending functionality - keep it simple, testable, and dependency-free!