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