Voicemail processing – Part 2
Home Assistant Automation
I have been using Extracting Voicemails for a while via ZOHO, but the account stopped working, so I decided to bit the bullet and set it all up on my own hosting.
I set up a new email box on my domain called voicemail.
I created a php script which polls a mailbox and pulls down any mail on that box, storing the attachments in a subfolder and then sends a web hook via NabuCasa to my Home Assistant, the web hook then triggers a message to be sent to my alert todo list and that in turn triggers messages to my phone.
Home Assistant Webhook Code
alias: Voicemail (rjt.org.uk)
description: ""
triggers:
- trigger: webhook
allowed_methods:
- POST
- PUT
- GET
local_only: false
webhook_id: voicemail
id: rjt
conditions: []
actions:
- variables:
filename: |-
{% if trigger.json.attachments | length > 0 %}
{{ trigger.json.attachments[0].url }}
{% else %}
N/A
{% endif %} {{trigger.json.attachments.url}}
type: >-
{% if 'New message' in trigger.json.subject %}voicemail{% else
%}missed_call{% endif %}
phone_no: "{{trigger.json.subject | regex_findall_index('\\d+.?\\d+') }}"
subject: >-
{{trigger.json.subject | replace('Fwd:','')}} {%- if type == 'voicemail'
%} [Listen to voicemail]({{filename}}) ({{ now().strftime('%Y-%m-%d
%H:%M:%S') }}) {%- endif %}
- target:
entity_id: todo.messages
data:
item: "{{'Missed Call' if type == 'missed_call' else 'New Voicemail'}}"
description: "{{subject}}"
due_datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
action: todo.add_item
mode: parallel
max: 10
PHP Script
If you want to use the script you will need to put in the correct values in config for your system.
<?php
/**
* POP3 Email Reader with Home Assistant Webhook Integration
* Reads emails, saves attachments, and sends notifications to Home Assistant
*/
// Configuration
$config = [
'pop3' => [
'host' => 'mail.example.com',
'port' => 995,
'username' => 'email@example.com',
'password' => 'password',
'ssl' => true
],
'storage' => [
'path' => __DIR__ . '/attachments/',
'base_url' => 'https://example.com/mail/attachments/'
],
'homeassistant' => [
'webhook_url' => 'https://hooks.nabu.casa/WEBHOOKID',
'token' => '' // Optional, if using authenticated webhook
],
'delete_after_read' => true // Set to true to delete emails after processing
];
// Create attachments directory if it doesn't exist
if (!file_exists($config['storage']['path'])) {
mkdir($config['storage']['path'], 0755, true);
}
/**
* Connect to POP3 mailbox
*/
function connectPOP3($config) {
$connection_string = '{' . $config['pop3']['host'] . ':' . $config['pop3']['port'] . '/pop3';
if ($config['pop3']['ssl']) {
$connection_string .= '/ssl/novalidate-cert';
}
$connection_string .= '}INBOX';
$mailbox = imap_open($connection_string, $config['pop3']['username'], $config['pop3']['password']);
if (!$mailbox) {
throw new Exception('Cannot connect to mailbox: ' . imap_last_error());
}
return $mailbox;
}
/**
* Decode email header
*/
function decodeHeader($header) {
$decoded = imap_mime_header_decode($header);
$result = '';
foreach ($decoded as $part) {
$result .= $part->text;
}
return $result;
}
/**
* Get email structure and extract attachments
*/
function processEmail($mailbox, $email_number, $config) {
$structure = imap_fetchstructure($mailbox, $email_number);
$header = imap_headerinfo($mailbox, $email_number);
$subject = isset($header->subject) ? decodeHeader($header->subject) : 'No Subject';
$from = isset($header->from[0]) ? $header->from[0]->mailbox . '@' . $header->from[0]->host : 'unknown';
$date = isset($header->date) ? $header->date : date('r');
$attachments = [];
if (isset($structure->parts) && count($structure->parts)) {
for ($i = 0; $i < count($structure->parts); $i++) {
$part = $structure->parts[$i];
// Check if this part is an attachment
if (isset($part->disposition) && strtolower($part->disposition) == 'attachment') {
$attachment = processAttachment($mailbox, $email_number, $part, $i + 1, $config);
if ($attachment) {
$attachments[] = $attachment;
}
} elseif (isset($part->dparameters)) {
foreach ($part->dparameters as $object) {
if (strtolower($object->attribute) == 'filename') {
$attachment = processAttachment($mailbox, $email_number, $part, $i + 1, $config);
if ($attachment) {
$attachments[] = $attachment;
}
break;
}
}
}
}
}
return [
'subject' => $subject,
'from' => $from,
'date' => $date,
'attachments' => $attachments
];
}
/**
* Process and save individual attachment
*/
function processAttachment($mailbox, $email_number, $part, $part_number, $config) {
$filename = '';
// Get filename
if (isset($part->dparameters)) {
foreach ($part->dparameters as $object) {
if (strtolower($object->attribute) == 'filename') {
$filename = $object->value;
break;
}
}
}
if (!$filename && isset($part->parameters)) {
foreach ($part->parameters as $object) {
if (strtolower($object->attribute) == 'name') {
$filename = $object->value;
break;
}
}
}
if (!$filename) {
return null;
}
// Decode filename
$filename = decodeHeader($filename);
// Sanitize filename
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename);
// Add timestamp to make unique
$unique_filename = date('YmdHis') . '_' . $filename;
$filepath = $config['storage']['path'] . $unique_filename;
// Fetch attachment data
$attachment_data = imap_fetchbody($mailbox, $email_number, $part_number);
// Decode based on encoding
if (isset($part->encoding)) {
switch ($part->encoding) {
case 3: // BASE64
$attachment_data = base64_decode($attachment_data);
break;
case 4: // QUOTED-PRINTABLE
$attachment_data = quoted_printable_decode($attachment_data);
break;
}
}
// Save file
if (file_put_contents($filepath, $attachment_data)) {
return [
'filename' => $filename,
'unique_filename' => $unique_filename,
'filepath' => $filepath,
'url' => $config['storage']['base_url'] . $unique_filename,
'size' => filesize($filepath)
];
}
return null;
}
/**
* Send webhook to Home Assistant
*/
function sendHomeAssistantWebhook($email_data, $config) {
$payload = [
'subject' => $email_data['subject'],
'from' => $email_data['from'],
'date' => $email_data['date'],
'attachments' => $email_data['attachments']
];
$ch = curl_init($config['homeassistant']['webhook_url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
/* Add authorization header if token is provided
if (!empty($config['homeassistant']['token'])) {
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $config['homeassistant']['token']
]);
}
*/
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $http_code;
return $http_code >= 200 && $http_code < 300;
}
// Main execution
try {
echo date('Y-m-d H:i:s')." Connecting to POP3 mailbox... ";
$mailbox = connectPOP3($config);
$num_messages = imap_num_msg($mailbox);
echo "Found {$num_messages} messages.\n";
if ($num_messages > 0) {
for ($i = 1; $i <= $num_messages; $i++) {
echo "Processing email {$i}/{$num_messages}...\n";
$email_data = processEmail($mailbox, $i, $config);
echo " Subject: {$email_data['subject']}\n";
echo " From: {$email_data['from']}\n";
echo " Attachments: " . count($email_data['attachments']) . "\n";
if (count($email_data['attachments']) > 0) {
foreach ($email_data['attachments'] as $att) {
echo " - {$att['filename']} ({$att['size']} bytes) -> {$att['url']}\n";
}
} else {
echo " No attachments found.\n";
}
// Send webhook to Home Assistant
echo " Sending webhook to Home Assistant...\n";
if (sendHomeAssistantWebhook($email_data, $config)) {
echo " ? Webhook sent successfully!\n";
} else {
echo " ? Failed to send webhook.\n";
}
// Delete email if configured
if ($config['delete_after_read']) {
imap_delete($mailbox, $i);
echo " Email marked for deletion.\n";
}
// echo "\n";
}
// Expunge deleted messages
if ($config['delete_after_read']) {
imap_expunge($mailbox);
}
}
imap_close($mailbox);
// echo "Done!\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
?>
Cron Job
The final step was to set up a cron job to run every minute to poll the mailbox. Replace PATHTOMAIL with the correct path for your server.
* * * * * /usr/local/bin/php /PATHTOMAIL/mail/read.php >> /home/taubmano/rjt.org.uk/mail/email_reader.log 2>&1