Building an AI-Enabled Automated Email Summary System with CI/CD

Semaphore
10 min readOct 15, 2024

--

Keeping users engaged is crucial to any application’s success. Automated email summaries provide an effective way to achieve this by delivering curated content that keeps users connected to your platform. In this tutorial, we’ll guide you through building an AI-enabled automated email summary system with continuous integration and deployment (CI/CD) using Semaphore.

Prerequisites

Before starting this tutorial, you should have:

All of the code for this article is available at ajcwebdev/semaphore-ai-email. Begin by cloning this repo, creating a .env file, and installing the project’s dependencies:

git clone https://github.com/ajcwebdev/semaphore-ai-email
cd semaphore-ai-email
cp .env.example .env
npm i

Step 1: Setting Up the Email Service and Templates

We’ll begin by creating the foundation of our email summary system: the email service and templates. This step involves setting up Nodemailer for email sending and designing responsive HTML templates for our summaries.

First, open the file named send-email.js. This script will handle the email sending process:

// send-email.js
import nodemailer from 'nodemailer'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import fs from 'fs/promises'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
async function sendEmail() {
let transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: 587,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
})
const htmlTemplate = await fs.readFile(join(__dirname, 'email-template.html'), 'utf-8') let mailOptions = {
from: `"Test Email" <${process.env.EMAIL_USER}>`,
to: process.env.EMAIL_USER,
subject: 'Weekly Update',
html: htmlTemplate
}
let info = await transporter.sendMail(mailOptions)
console.log('Message sent: %s', info.messageId)
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info))
}
async function main() {
try {
await sendEmail()
console.log('Email sent successfully. Waiting 1 week before sending the next one...')
} catch (error) {
console.error('Failed to send email:', error)
}
await new Promise(resolve => setTimeout(resolve, 7 * 24 * 60 * 60 * 1000))
}
main().catch(console.error)

This script sets up a Nodemailer transporter using Ethereal Email, a catch-all email testing service. Ethereal is ideal for development as it captures emails without actually sending them, providing a preview URL for each message.

Let’s break down the key components:

  • Imports and setup: We import necessary modules and set up file path utilities.
  • sendEmail function: Creates a Nodemailer transporter, reads our HTML template and CSS file, and sends the email.
  • File reading: We use fs.promises to read the template and CSS files asynchronously.
  • Email configuration: The email is configured with a from address, to address, subject, HTML content, and CSS attachment.
  • Logging: After sending, we log the message ID and preview URL provided by Ethereal.
  • main function: Runs in a loop, sending an email once every week. This simulates a scheduled task, which we’ll implement properly with Semaphore later.

This script references two articles selected and saved in files called ARTICLE_1.js and ARTICLE_2.js. You can use the examples provided in my repo or replace them with your own. The files are structured like so (see example repo for entire file contents):

// ARTICLE_1.js
export const ARTICLE_1 = {
title: "Why you should write your own LLM benchmarks - with Nicholas Carlini, Google DeepMind",
content: `<![CDATA[ <p>Today's guest, Nicholas Carlini, a research scientist at DeepMind, argues that we should be focusing more on what AI can do for us <strong>individually</strong>, rather than trying to have an answer for everyone.</p><p><strong>...`,
}
// ARTICLE_2.jsexport const ARTICLE_2 = {
title: "Why you should write your own LLM benchmarks - with Nicholas Carlini, Google DeepMind",
content: `<![CDATA[ <p><a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/Betteridge%27s_law_of_headlines\">Betteridge's law</a> says no: with seemingly infinite flavors of RAG, and >2million token context + prompt caching from Anthropic/Deepmind/Deepseek, it's reasonable to believe that \"in context learning is all you need\".</p>...`,
}

Next, open the HTML email template named email-template.html. The AI summaries we’ll generate in the next section are contained within the main tags.

<!-- email-template.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Weekly AI Newsletter</title>
<style>
@media screen and (max-width: 600px) {
.container {
width: 100% !important;
}
.content {
padding: 10px !important;
}
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: #f4f4f4;
}
.content {
padding: 20px;
}
.header {
background-color: #2c3e50;
color: #ffffff;
text-align: center;
padding: 20px;
}
.section {
background-color: #ffffff;
margin-bottom: 20px;
padding: 15px;
border-radius: 5px;
}
.footer {
background-color: #34495e;
color: #ffffff;
text-align: center;
padding: 10px;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>Weekly AI Newsletter</h1>
<p>Your weekly digest of AI news and insights</p>
</header>
<main class="content">
<section class="section">
<h2>Top Articles</h2>
<article>
<h3>Why you should write your own LLM benchmarks — with Nicholas Carlini, Google DeepMind</h3>
<p>The article features an interview with Nicholas Carlini, a research scientist at DeepMind, discussing his views on AI and his work in AI security. Key points include:</p>
<ul>
<li>Carlini wrote a viral blog post "How I Use AI" detailing practical ways he uses AI tools in his daily work as a security researcher. He emphasizes focusing on what AI can do for individuals rather than trying to have a universal answer.</li>
<li>He created a domain-specific language for writing personalized LLM benchmarks, arguing that more people should make benchmarks tailored to their specific needs rather than relying solely on standardized tests.</li>
<li>Carlini discusses his research in AI security, including work on data poisoning, model stealing, and extracting training data from language models. He emphasizes the importance of studying real-world vulnerabilities in AI systems.</li>
<li>He explains his preference for focusing on attacking rather than defending AI systems, stating that he finds it more motivating and believes he can contribute more effectively by identifying vulnerabilities.</li>
</ul>
</article>
<article>
<h3>Is finetuning GPT4o worth it? — with Alistair Pullen, Cosine (Genie)</h3>
<p>This article discusses an interview with Alistair Pullen, CEO and co-founder of Cosine, about their new AI coding agent called Genie. Key points include:</p>
<ul>
<li>Genie achieved state-of-the-art results on the SWE-Bench coding benchmark, outperforming other solutions by a significant margin.</li>
<li>Cosine worked closely with OpenAI to fine-tune GPT-4 on billions of tokens of synthetic code data, focusing on reproducing the process of how human software engineers work rather than just training on finished code.</li>
<li>Genie uses a four-stage workflow: finding files, planning actions, writing code, and running tests. It can interact with GitHub and CI systems to execute and test code changes.</li>
<li>The team faced challenges in areas like code retrieval, context window limitations, and balancing different programming languages in the training data.</li>
<li>Cosine is looking to expand Genie's capabilities, increase their dataset size, and potentially customize models for specific customers' codebases.</li>
</ul>
</article>
</section>
</main>
<footer class="footer">
<p>&copy; 2024 Weekly AI Newsletter. All rights reserved.</p>
<p>
<a href="#">Unsubscribe</a> |
<a href="#">View in browser</a> |
<a href="#">Privacy Policy</a>
</p>
</footer>
</div>
</body>
</html>

This HTML template provides the structure for our email. It includes:

  • Responsive meta tag for proper rendering on mobile devices
  • Header with the newsletter title
  • Main content area with placeholders for article summaries
  • Footer with copyright information and standard email links

The CSS defines the styles for our email template. Key features include:

  • Media query for responsiveness on smaller screens
  • Consistent typography and color scheme
  • Container with a maximum width for better readability
  • Distinct styling for header, content sections, and footer
  • Subtle use of background colors and padding to separate different areas of the email

In the next step, we’ll focus on content aggregation and processing, where we’ll use AI to generate summaries for our newsletter.

Step 2: Content Aggregation and Processing

In this step, we’ll use Anthropic’s Claude API to automatically summarize articles for our email digest. This process involves interacting with an advanced language model to generate concise, informative summaries of longer articles.

Open the file named claude.js:

// claude.js
import Anthropic from '@anthropic-ai/sdk'
import { ARTICLE_1 } from './ARTICLE_1.js'
import { ARTICLE_2 } from './ARTICLE_2.js'
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
})
async function summarizeArticle(article) {
const prompt = `Please provide a one to two paragraph summary of the following article:
${article}Summary:` const response = await anthropic.messages.create({
model: "claude-3-5-sonnet-20240620",
max_tokens: 300,
messages: [
{ role: "user", content: prompt }
]
})
return response.content[0].text
}
async function processArticles(articles) {
const summaries = []
for (const article of articles) {
const { title, content } = article
const summary = await summarizeArticle(content)
summaries.push({
title,
summary
})
console.log(`Summarized: ${title}`)
}
return summaries
}
async function main() {
const articles = [
{
title: ARTICLE_1.title,
content: ARTICLE_1.content
},
{
title: ARTICLE_2.title,
content: ARTICLE_2.content
}
]
try {
const results = await processArticles(articles)
for (const result of results) {
console.log(`\nTitle: ${result.title}`)
console.log(`Summary: ${result.summary}`)
console.log("-".repeat(50))
}
} catch (error) {
console.error("An error occurred:", error)
}
}
main()

Let’s break down this script and examine its key components:

  1. Importing Dependencies and Articles: We import the Anthropic SDK and two article contents from separate files. In a production environment, you might fetch these articles from a database or API.
  2. Initializing the Anthropic Client: We create an instance of the Anthropic client, using an API key stored in an environment variable for security.
  3. Article Summarization Function: The summarizeArticle function takes an article’s content and sends it to Claude with a specific prompt. We use the “claude-3-5-sonnet-20240620” model and limit the response to 300 tokens for concise summaries. The function returns the generated summary.
  4. Article Processing Function: The processArticles function iterates through an array of articles, calling summarizeArticle for each one. It collects the title and generated summary for each article and returns an array of these summaries.
  5. Main Execution Function: The main function defines an array of articles (in a real-world scenario, this data might come from an external source). It then calls processArticles and logs the results. Error handling is implemented to catch and log any issues that occur during processing.
  6. Execution: Finally, we call the main function to start the summarization process.

Open the .env file and include your Anthropic API key:

ANTHROPIC_API_KEY=""

Run the script with the following command:

npm run claude

This script demonstrates using AI for content processing. By using Claude’s natural language understanding capabilities, we can automatically generate concise summaries of lengthy articles. The next step will involve setting up automated deployment with Semaphore.

Step 3: Automating the Build and Deploy Process with Semaphore

In this step, we’ll set up a continuous integration and deployment (CI/CD) pipeline using Semaphore. This will automate our build process and schedule our weekly email digest.

The semaphore.yml file in the .semaphore directory contains the specific steps for sending the email. This allows separating the weekly email task from the regular CI/CD pipeline.

# .semaphore/semaphore.yml
version: v1.0
name: Send Weekly Email
agent:
machine:
type: e1-standard-2
os_image: ubuntu2004
blocks:
- name: Send Email
task:
secrets:
- name: email-credentials
jobs:
- name: Send Email
commands:
- checkout
- sem-version node 20
- npm install nodemailer
- node send-email.js
  1. Set up Semaphore project: To set up your Semaphore project, first create a Semaphore account, open your organization settings, and navigate to the Secrets section:
  • 2. Create secrets: Click “New Secret” and name the secret email-credentials. Set your test email to EMAIL_USER, your password to EMAIL_PASS, and your email host provider to EMAIL_HOST.
  • 3. Save and connect: Click “Save Secret.” Next, create a new project and connect the project to your GitHub repository:
  • 4. Run workflow: This will start running your workflow:
  • 5. View logs: Click “Send Email” to view the workflow logs:
  • 6. Check results: Your logs should include a message saying, “Message sent” along with a preview URL. Open the preview URL to view the example email:

This separate pipeline file focuses solely on the tasks required to send your weekly email. By setting up this Semaphore configuration, you’ve automated your build process and scheduled your weekly email digest. Remember to regularly review your Semaphore logs to ensure everything is running smoothly and to catch any potential issues early.

Step 4: Analyzing and Optimizing Summary Emails

To improve our email summaries over time, we should implement analytics to track open rates and engagement. We can set up A/B testing for subject lines and content layouts. Here’s how:

  1. Create multiple versions of your email template.
  2. Modify your Semaphore pipeline to randomly select a template version for each send.
  3. Use an analytics service to track which versions perform better. Popular options include Optimizely and VWO.
  4. Periodically review the data and update your templates based on the results.

Step 5: Scaling, Cost Optimization, and Deliverability

As your user base grows, consider these strategies for scaling:

  • Use a message queue (like RabbitMQ or Redis) to handle increased load.
  • Implement caching to reduce API calls and improve performance.
  • Optimize cloud resource usage.

A caching mechanism can significantly reduce the number of API calls to Claude, lowering costs and improving response times.

To ensure email deliverability and prevent your domain from being blacklisted for spam, implement the following best practices:

  1. Monitor delivery metrics: Keep a close eye on your email analytics, particularly failed deliveries and spam rejections. Use tools like SendGrid or Mailgun that provide detailed delivery reports.
  2. Maintain list hygiene: Regularly clean your mailing list by removing inactive subscribers and hard bounces. This improves your sender reputation and reduces the risk of being flagged as spam.
  3. Implement authentication: Set up SPF, DKIM, and DMARC records for your domain to verify your identity as a legitimate sender.
  4. Optimize content: Avoid spam trigger words in your subject lines and content. Use a balanced text-to-image ratio and include an easy unsubscribe option.
  5. Warm up your IP: If you’re using a new IP address, gradually increase your sending volume to establish a positive reputation with ISPs.
  6. Use feedback loops: Subscribe to feedback loops offered by major ISPs to receive notifications when recipients mark your emails as spam.
  7. Test before sending: Use tools like Mail-Tester to check your email’s spam score before sending it to your entire list.

By implementing these practices and closely monitoring your email performance, you can maintain a strong sender reputation, ensure high deliverability rates, and prevent your domain from being blacklisted.

Conclusion

We’ve built an AI-enabled automated email summary system with CI/CD capabilities. This system aggregates content, uses AI to generate summaries, and sends personalized email digests to users. By leveraging Semaphore’s CI/CD pipeline, we ensure consistent, automated deployments.

To maintain and evolve this system:

  • Regularly update dependencies and security patches.
  • Monitor performance metrics and user engagement.
  • Continuously refine your AI prompts for better summaries.
  • Consider adding more content sources and personalization features.

Remember, while we used Ethereal Email for testing, you’ll want to switch to a production-ready email service provider when you’re ready to send to real users.

Start building your own automated email summary system today by setting up your Semaphore pipeline and integrating AI-powered content summarization.

Additional Resources

To further your understanding of the technologies and concepts used in this tutorial, consider exploring these resources:

By following this tutorial and exploring these additional resources, you’ll be well-equipped to create and maintain a sophisticated, AI-powered email summary system that keeps your users engaged and informed.

Originally published at https://semaphoreci.com on October 15, 2024.

--

--

Semaphore
Semaphore

Written by Semaphore

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

No responses yet