Working with E-Mails
Reaktors can receive and reply to e-mails through the @operaide/mail package. When someone sends an e-mail to a configured address, Operaide parses the message and routes it to your Reaktor as structured input. Your Reaktor can then process the content, work with attachments, and send a reply — all within the same workflow.
Overview
| Pattern | When to Use |
|---|---|
Mail Reaktor (registerMailReaktorDefinition) | Receive incoming e-mails as Reaktor input |
Send (aktorSendMail) | Send a new e-mail from within a Reaktor |
Reply (aktorReplyToMail) | Reply to an incoming e-mail while preserving the thread |
Attachments (aktorMapFilesToBase64) | Convert PDF attachments to base64 for further processing |
All e-mail Aktors are imported from @operaide/mail.
Receiving E-Mails
Use registerMailReaktorDefinition to create a Reaktor that receives incoming e-mails. It is a convenience wrapper around registerReaktorDefinition that pre-configures the input schema to the ParsedMail type and disables chat mode.
import { createAktorComposition } from '@operaide/aktor';
import { registerMailReaktorDefinition } from '@operaide/mail';
const aktorMyMailHandler = createAktorComposition('aktorMyMailHandler', (params) => {
// params is a ParsedMail object
// ... your workflow logic here
});
registerMailReaktorDefinition({
reaktorDefinitionId: 'my-mail-handler',
label: 'My Mail Handler',
description: 'Processes incoming e-mails.',
aktor: aktorMyMailHandler,
});
The ParsedMail Input
When an e-mail arrives, the platform parses it and passes a ParsedMail object to your Aktor composition callback. The most important properties are:
| Property | Type | Description |
|---|---|---|
sender | string | E-mail address of the sender |
recipient | string | E-mail address the message was sent to |
from | string | Full "From" header (may include display name) |
subject | string | Subject line |
bodyPlain | string | Plain-text body |
bodyHtml | string (optional) | HTML body |
messageId | string | Unique message ID — pass this to aktorReplyToMail for threading |
messageHeaders | string | Raw message headers (useful for extracting In-Reply-To) |
files | MailAttachment[] | Attached files (see Working with Attachments) |
attachmentCount | number (optional) | Number of attachments |
Sending E-Mails
Use aktorSendMail to send a new e-mail that is not a reply to an existing thread.
import { aktorSendMail } from '@operaide/mail';
const result = aktorSendMail({
from: 'noreply@example.com',
to: 'recipient@example.com',
subject: 'Your report is ready',
htmlBody: '<p>The report has been generated.</p>',
});
| Parameter | Type | Description |
|---|---|---|
from | string | Sender e-mail address |
to | string | Recipient e-mail address |
subject | string | Subject line |
htmlBody | string | HTML body of the e-mail |
Replying to E-Mails
Use aktorReplyToMail to reply to an incoming e-mail. It accepts the same parameters as aktorSendMail plus a messageId that links the reply to the original thread. The subject is automatically prefixed with "Re: ".
import { aktorReplyToMail } from '@operaide/mail';
const reply = aktorReplyToMail({
from: params.recipient,
to: params.sender,
subject: params.subject,
messageId: params.messageId,
htmlBody: '<p>Thank you for your message.</p>',
});
| Parameter | Type | Description |
|---|---|---|
from | string | Sender e-mail address (typically the original recipient) |
to | string | Recipient e-mail address (typically the original sender) |
subject | string | Original subject — "Re: " is added automatically |
messageId | string | The messageId from the incoming ParsedMail |
htmlBody | string | HTML body of the reply |
Always pass the original messageId when replying. This ensures the reply appears in the same thread in the recipient's mail client.
Working with Attachments
The files array on ParsedMail contains MailAttachment objects with these properties:
| Property | Type | Description |
|---|---|---|
originalname | string | File name |
mimetype | string | MIME type (e.g. application/pdf) |
buffer | Buffer | Raw file content |
size | number | File size in bytes |
Converting PDFs to Base64
Use aktorMapFilesToBase64 to extract PDF attachments as base64-encoded strings. It filters for application/pdf files and ignores other MIME types.
import { aktorMapFilesToBase64 } from '@operaide/mail';
const base64Pdfs = aktorMapFilesToBase64({ files: params.files });
// returns string[] — one base64 string per PDF attachment
To convert the PDFs to Markdown with Document Intelligence, use aktorConvertMultiplePDFsToMarkdown for the full array, or aktorConvertPDFToMarkdown if you only need a single file:
import { aktorConvertMultiplePDFsToMarkdown } from '@operaide/document';
const markdowns = aktorConvertMultiplePDFsToMarkdown({
pdfs: base64Pdfs,
connectionName: 'default',
});
Full Example: E-Mail to Data Extraction
This example receives an e-mail with a PDF attachment, converts it to Markdown, extracts structured data using AI, and replies with the result.
import { aktorJoinStrings, aktorSetting, createAktorComposition } from '@operaide/aktor';
import { aktorAICall, aktorAISettingProviderModel, aktorMessagesFromSystemAndUser } from '@operaide/ai';
import { aktorConvertMultiplePDFsToMarkdown } from '@operaide/document';
import { aktorMapFilesToBase64, aktorReplyToMail, registerMailReaktorDefinition } from '@operaide/mail';
import { z } from 'zod';
const aktorEmailDataExtractor = createAktorComposition('aktorEmailDataExtractor', (params) => {
// Convert PDF attachments to base64
const base64Pdfs = aktorMapFilesToBase64({ files: params.files });
// Convert to Markdown via Document Intelligence
const markdowns = aktorConvertMultiplePDFsToMarkdown({
pdfs: base64Pdfs,
connectionName: aktorSetting(z.string(), 'default', 'Document Intelligence Connection'),
});
// Join multiple markdowns into a single string
const document = aktorJoinStrings({ strings: markdowns });
// Extract structured data with AI
const extracted = aktorAICall({
messages: aktorMessagesFromSystemAndUser({
system: aktorSetting(
z.string().describe('[textarea] System Prompt'),
'Extract the order number, date, and line items from the document.',
'Extraction Prompt'
),
user: document,
}),
providerModel: aktorAISettingProviderModel(),
});
// Reply with the result
return aktorReplyToMail({
from: params.recipient,
to: params.sender,
subject: params.subject,
messageId: params.messageId,
htmlBody: extracted,
});
});
registerMailReaktorDefinition({
reaktorDefinitionId: 'email-data-extractor',
label: 'Email Data Extractor',
description: 'Extracts data from e-mail attachments and replies with the result.',
aktor: aktorEmailDataExtractor,
});
Best Practices
-
Use
registerMailReaktorDefinitioninstead of the genericregisterReaktorDefinition— it pre-configures theParsedMailinput schema and disables chat mode for you. -
Always include
messageIdwhen replying so the response threads correctly in the recipient's mail client. -
Swap
fromandtowhen replying: setfromtoparams.recipient(the address that received the original e-mail) andtotoparams.sender. -
Wrap runtime settings in
aktorSetting(connection names, prompts, URLs) so operators can configure them at deployment time without code changes. -
Combine with Document Intelligence for PDF attachments — use
aktorMapFilesToBase64to get base64 data, then pass it toaktorConvertMultiplePDFsToMarkdownoraktorConvertPDFToMarkdownto produce Markdown that an AI model can process.