Skip to content

Commit d3c3a3e

Browse files
authored
send email with ai (#91)
* send email with ai * schedule workflow * fix openai * workflow failure simulate * restack ai update
1 parent d58bfcb commit d3c3a3e

14 files changed

+835
-0
lines changed

email_sender/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
SENDGRID_API_KEY=your-sendgrid-api-key
3+
OPENAI_API_KEY=your-openai-api-key
4+

email_sender/README.md

Whitespace-only changes.

email_sender/poetry.lock

Lines changed: 547 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

email_sender/pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[tool.poetry]
2+
name = "email_sender"
3+
version = "0.0.1"
4+
description = "Send emails with sendgrid"
5+
authors = [
6+
"Restack Team <[email protected]>",
7+
]
8+
readme = "README.md"
9+
packages = [{include = "src"}]
10+
11+
[tool.poetry.dependencies]
12+
python = "3.12.7"
13+
pydantic = "2.9.2"
14+
python-dotenv = "1.0.1"
15+
sendgrid = "6.11.0"
16+
openai = "1.56.2"
17+
restack-ai = "0.0.42"
18+
19+
[build-system]
20+
requires = ["poetry-core"]
21+
build-backend = "poetry.core.masonry.api"
22+
23+
[tool.poetry.scripts]
24+
services = "src.services:run_services"
25+
schedule = "schedule_workflow:run_schedule_workflow"
26+
schedule_failure = "schedule_workflow_failure:run_schedule_workflow_failure"

email_sender/schedule_workflow.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import asyncio
2+
import time
3+
from restack_ai import Restack
4+
from dataclasses import dataclass
5+
import os
6+
from dotenv import load_dotenv
7+
8+
load_dotenv()
9+
10+
@dataclass
11+
class InputParams:
12+
email_context: str
13+
subject: str
14+
to: str
15+
16+
async def main():
17+
client = Restack()
18+
19+
workflow_id = f"{int(time.time() * 1000)}-SendEmailWorkflow"
20+
to_email = os.getenv("TO_EMAIL")
21+
if not to_email:
22+
raise Exception("TO_EMAIL environment variable is not set")
23+
24+
run_id = await client.schedule_workflow(
25+
workflow_name="SendEmailWorkflow",
26+
workflow_id=workflow_id,
27+
input={
28+
"email_context": "This email should contain a greeting. And telling user we have launched a new AI feature with Restack workflows. Workflows now offer logging and automatic retries when one of its steps fails. Name of user is not provided. You can set as goodbye message on the email just say 'Best regards' or something like that. No need to mention name of user or name of person sending the email.",
29+
"subject": "Hello from Restack",
30+
"to": to_email
31+
}
32+
)
33+
34+
await client.get_workflow_result(
35+
workflow_id=workflow_id,
36+
run_id=run_id
37+
)
38+
39+
exit(0)
40+
41+
def run_schedule_workflow():
42+
asyncio.run(main())
43+
44+
if __name__ == "__main__":
45+
run_schedule_workflow()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import asyncio
2+
import time
3+
from restack_ai import Restack
4+
from dataclasses import dataclass
5+
import os
6+
from dotenv import load_dotenv
7+
8+
load_dotenv()
9+
10+
@dataclass
11+
class InputParams:
12+
email_context: str
13+
subject: str
14+
to: str
15+
16+
async def main():
17+
client = Restack()
18+
19+
workflow_id = f"{int(time.time() * 1000)}-SendEmailWorkflow"
20+
to_email = os.getenv("TO_EMAIL")
21+
if not to_email:
22+
raise Exception("TO_EMAIL environment variable is not set")
23+
24+
run_id = await client.schedule_workflow(
25+
workflow_name="SendEmailWorkflow",
26+
workflow_id=workflow_id,
27+
input={
28+
"email_context": "This email should contain a greeting. And telling user we have launched a new AI feature with Restack workflows. Workflows now offer logging and automatic retries when one of its steps fails. Name of user is not provided. You can set as goodbye message on the email just say 'Best regards' or something like that. No need to mention name of user or name of person sending the email.",
29+
"subject": "Hello from Restack",
30+
"to": to_email,
31+
"simulate_failure": True
32+
}
33+
)
34+
35+
await client.get_workflow_result(
36+
workflow_id=workflow_id,
37+
run_id=run_id
38+
)
39+
40+
exit(0)
41+
42+
def run_schedule_workflow_failure():
43+
asyncio.run(main())
44+
45+
if __name__ == "__main__":
46+
run_schedule_workflow_failure()

email_sender/src/__init__.py

Whitespace-only changes.

email_sender/src/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
from restack_ai import Restack
3+
from restack_ai.restack import CloudConnectionOptions
4+
from dotenv import load_dotenv
5+
6+
# Load environment variables from a .env file
7+
load_dotenv()
8+
9+
10+
engine_id = os.getenv("RESTACK_ENGINE_ID")
11+
address = os.getenv("RESTACK_ENGINE_ADDRESS")
12+
api_key = os.getenv("RESTACK_ENGINE_API_KEY")
13+
14+
connection_options = CloudConnectionOptions(
15+
engine_id=engine_id,
16+
address=address,
17+
api_key=api_key
18+
)
19+
client = Restack(connection_options)

email_sender/src/functions/__init__.py

Whitespace-only changes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from restack_ai.function import function, log, FunctionFailure
2+
from dataclasses import dataclass
3+
from openai import OpenAI
4+
import os
5+
from dotenv import load_dotenv
6+
7+
load_dotenv()
8+
9+
tries = 0
10+
11+
@dataclass
12+
class GenerateEmailInput:
13+
email_context: str
14+
simulate_failure: bool = False
15+
16+
@function.defn()
17+
async def generate_email_content(input: GenerateEmailInput):
18+
global tries
19+
20+
if input.simulate_failure and tries == 0:
21+
tries += 1
22+
raise FunctionFailure("Simulated failure", non_retryable=False)
23+
24+
if (os.environ.get("OPENAI_API_KEY") is None):
25+
raise FunctionFailure("OPENAI_API_KEY is not set", non_retryable=True)
26+
27+
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
28+
29+
response = client.chat.completions.create(
30+
model="gpt-4",
31+
messages=[
32+
{
33+
"role": "system",
34+
"content": "You are a helpful assistant that generates short emails based on the provided context."
35+
},
36+
{
37+
"role": "user",
38+
"content": f"Generate a short email based on the following context: {input.email_context}"
39+
}
40+
],
41+
max_tokens=150
42+
)
43+
44+
return response.choices[0].message.content
45+

0 commit comments

Comments
 (0)