{"id":13994,"date":"2025-11-07T10:24:50","date_gmt":"2025-11-07T10:24:50","guid":{"rendered":"https:\/\/www.rjt.org.uk\/home\/?post_type=home_assistant_tip&#038;p=13994"},"modified":"2025-11-07T10:24:52","modified_gmt":"2025-11-07T10:24:52","slug":"voicemail-processing-part-2","status":"publish","type":"home_assistant_tip","link":"https:\/\/www.rjt.org.uk\/home\/archives\/home-assistant-tip\/voicemail-processing-part-2\/","title":{"rendered":"Voicemail processing &#8211; Part 2"},"content":{"rendered":"\n<p>I have been using <a href=\"https:\/\/www.rjt.org.uk\/home\/archives\/home-assistant-tip\/extracting-voicemails-from-email\/\">Extracting Voicemails<\/a> 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.<\/p>\n\n\n\n<p>I set up a new email box on my domain called voicemail.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Home Assistant Webhook Code<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">alias: Voicemail (rjt.org.uk)\ndescription: \"\"\ntriggers:\n  - trigger: webhook\n    allowed_methods:\n      - POST\n      - PUT\n      - GET\n    local_only: false\n    webhook_id: voicemail\n    id: rjt\nconditions: []\nactions:\n  - variables:\n      filename: |-\n        {% if trigger.json.attachments | length > 0 %}\n          {{ trigger.json.attachments[0].url }}\n        {% else %}\n          N\/A\n        {% endif %} {{trigger.json.attachments.url}}\n      type: >-\n        {% if 'New message' in  trigger.json.subject %}voicemail{% else\n        %}missed_call{% endif %}\n      phone_no: \"{{trigger.json.subject | regex_findall_index('\\\\d+.?\\\\d+') }}\"\n      subject: >-\n        {{trigger.json.subject | replace('Fwd:','')}} {%- if type == 'voicemail'\n        %} [Listen to voicemail]({{filename}}) ({{ now().strftime('%Y-%m-%d\n        %H:%M:%S') }}) {%- endif %}\n  - target:\n      entity_id: todo.messages\n    data:\n      item: \"{{'Missed Call' if type == 'missed_call' else 'New Voicemail'}}\"\n      description: \"{{subject}}\"\n      due_datetime: \"{{ now().strftime('%Y-%m-%d %H:%M:%S') }}\"\n    action: todo.add_item\nmode: parallel\nmax: 10<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">PHP Script<\/h2>\n\n\n\n<p>If you want to use the script you will need to put in the correct values in config for your system.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;?php\n\/**\n * POP3 Email Reader with Home Assistant Webhook Integration\n * Reads emails, saves attachments, and sends notifications to Home Assistant\n *\/\n\n\/\/ Configuration\n$config = [\n    'pop3' => [\n        'host' => 'mail.example.com',\n        'port' => 995,\n        'username' => 'email@example.com',\n        'password' => 'password',\n        'ssl' => true\n    ],\n    'storage' => [\n        'path' => __DIR__ . '\/attachments\/',\n        'base_url' => 'https:\/\/example.com\/mail\/attachments\/'\n    ],\n    'homeassistant' => [\n        'webhook_url' => 'https:\/\/hooks.nabu.casa\/WEBHOOKID',\n        'token' => '' \/\/ Optional, if using authenticated webhook\n    ],\n    'delete_after_read' => true \/\/ Set to true to delete emails after processing\n];\n\n\/\/ Create attachments directory if it doesn't exist\nif (!file_exists($config['storage']['path'])) {\n    mkdir($config['storage']['path'], 0755, true);\n}\n\n\/**\n * Connect to POP3 mailbox\n *\/\nfunction connectPOP3($config) {\n    $connection_string = '{' . $config['pop3']['host'] . ':' . $config['pop3']['port'] . '\/pop3';\n    if ($config['pop3']['ssl']) {\n        $connection_string .= '\/ssl\/novalidate-cert';\n    }\n    $connection_string .= '}INBOX';\n    \n    $mailbox = imap_open($connection_string, $config['pop3']['username'], $config['pop3']['password']);\n    \n    if (!$mailbox) {\n        throw new Exception('Cannot connect to mailbox: ' . imap_last_error());\n    }\n    \n    return $mailbox;\n}\n\n\/**\n * Decode email header\n *\/\nfunction decodeHeader($header) {\n    $decoded = imap_mime_header_decode($header);\n    $result = '';\n    foreach ($decoded as $part) {\n        $result .= $part->text;\n    }\n    return $result;\n}\n\n\/**\n * Get email structure and extract attachments\n *\/\nfunction processEmail($mailbox, $email_number, $config) {\n    $structure = imap_fetchstructure($mailbox, $email_number);\n    $header = imap_headerinfo($mailbox, $email_number);\n    \n    $subject = isset($header->subject) ? decodeHeader($header->subject) : 'No Subject';\n    $from = isset($header->from[0]) ? $header->from[0]->mailbox . '@' . $header->from[0]->host : 'unknown';\n    $date = isset($header->date) ? $header->date : date('r');\n    \n    $attachments = [];\n    \n    if (isset($structure->parts) &amp;&amp; count($structure->parts)) {\n        for ($i = 0; $i &lt; count($structure->parts); $i++) {\n            $part = $structure->parts[$i];\n            \n            \/\/ Check if this part is an attachment\n            if (isset($part->disposition) &amp;&amp; strtolower($part->disposition) == 'attachment') {\n                $attachment = processAttachment($mailbox, $email_number, $part, $i + 1, $config);\n                if ($attachment) {\n                    $attachments[] = $attachment;\n                }\n            } elseif (isset($part->dparameters)) {\n                foreach ($part->dparameters as $object) {\n                    if (strtolower($object->attribute) == 'filename') {\n                        $attachment = processAttachment($mailbox, $email_number, $part, $i + 1, $config);\n                        if ($attachment) {\n                            $attachments[] = $attachment;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n    }\n    \n    return [\n        'subject' => $subject,\n        'from' => $from,\n        'date' => $date,\n        'attachments' => $attachments\n    ];\n}\n\n\/**\n * Process and save individual attachment\n *\/\nfunction processAttachment($mailbox, $email_number, $part, $part_number, $config) {\n    $filename = '';\n    \n    \/\/ Get filename\n    if (isset($part->dparameters)) {\n        foreach ($part->dparameters as $object) {\n            if (strtolower($object->attribute) == 'filename') {\n                $filename = $object->value;\n                break;\n            }\n        }\n    }\n    \n    if (!$filename &amp;&amp; isset($part->parameters)) {\n        foreach ($part->parameters as $object) {\n            if (strtolower($object->attribute) == 'name') {\n                $filename = $object->value;\n                break;\n            }\n        }\n    }\n    \n    if (!$filename) {\n        return null;\n    }\n    \n    \/\/ Decode filename\n    $filename = decodeHeader($filename);\n    \n    \/\/ Sanitize filename\n    $filename = preg_replace('\/[^a-zA-Z0-9._-]\/', '_', $filename);\n    \n    \/\/ Add timestamp to make unique\n    $unique_filename = date('YmdHis') . '_' . $filename;\n    $filepath = $config['storage']['path'] . $unique_filename;\n    \n    \/\/ Fetch attachment data\n    $attachment_data = imap_fetchbody($mailbox, $email_number, $part_number);\n    \n    \/\/ Decode based on encoding\n    if (isset($part->encoding)) {\n        switch ($part->encoding) {\n            case 3: \/\/ BASE64\n                $attachment_data = base64_decode($attachment_data);\n                break;\n            case 4: \/\/ QUOTED-PRINTABLE\n                $attachment_data = quoted_printable_decode($attachment_data);\n                break;\n        }\n    }\n    \n    \/\/ Save file\n    if (file_put_contents($filepath, $attachment_data)) {\n        return [\n            'filename' => $filename,\n            'unique_filename' => $unique_filename,\n            'filepath' => $filepath,\n            'url' => $config['storage']['base_url'] . $unique_filename,\n            'size' => filesize($filepath)\n        ];\n    }\n    \n    return null;\n}\n\n\/**\n * Send webhook to Home Assistant\n *\/\nfunction sendHomeAssistantWebhook($email_data, $config) {\n    $payload = [\n        'subject' => $email_data['subject'],\n        'from' => $email_data['from'],\n        'date' => $email_data['date'],\n        'attachments' => $email_data['attachments']\n    ];\n    \n    $ch = curl_init($config['homeassistant']['webhook_url']);\n    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n    curl_setopt($ch, CURLOPT_POST, true);\n    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));\n    curl_setopt($ch, CURLOPT_HTTPHEADER, [\n        'Content-Type: application\/json'\n    ]);\n    \n    \/* Add authorization header if token is provided\n    if (!empty($config['homeassistant']['token'])) {\n        curl_setopt($ch, CURLOPT_HTTPHEADER, [\n            'Content-Type: application\/json',\n            'Authorization: Bearer ' . $config['homeassistant']['token']\n        ]);\n    }\n    *\/\n    \n    $response = curl_exec($ch);\n    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n    curl_close($ch);\n    echo $http_code;\n    return $http_code >= 200 &amp;&amp; $http_code &lt; 300;\n}\n\n\/\/ Main execution\ntry {\n    echo date('Y-m-d H:i:s').\" Connecting to POP3 mailbox... \";\n    $mailbox = connectPOP3($config);\n    \n    $num_messages = imap_num_msg($mailbox);\n    echo \"Found {$num_messages} messages.\\n\";\n    \n    if ($num_messages > 0) {\n        for ($i = 1; $i &lt;= $num_messages; $i++) {\n            echo \"Processing email {$i}\/{$num_messages}...\\n\";\n            \n            $email_data = processEmail($mailbox, $i, $config);\n            \n            echo \"  Subject: {$email_data['subject']}\\n\";\n            echo \"  From: {$email_data['from']}\\n\";\n            echo \"  Attachments: \" . count($email_data['attachments']) . \"\\n\";\n            \n            if (count($email_data['attachments']) > 0) {\n                foreach ($email_data['attachments'] as $att) {\n                    echo \"    - {$att['filename']} ({$att['size']} bytes) -> {$att['url']}\\n\";\n                }\n                \n\n            } else {\n                echo \"  No attachments found.\\n\";\n            }\n\n                \/\/ Send webhook to Home Assistant\n                echo \"  Sending webhook to Home Assistant...\\n\";\n                if (sendHomeAssistantWebhook($email_data, $config)) {\n                    echo \"  ? Webhook sent successfully!\\n\";\n                } else {\n                    echo \"  ? Failed to send webhook.\\n\";\n                }\n            \n            \/\/ Delete email if configured\n            if ($config['delete_after_read']) {\n                imap_delete($mailbox, $i);\n                echo \"  Email marked for deletion.\\n\";\n            }\n            \n          \/\/  echo \"\\n\";\n        }\n        \n        \/\/ Expunge deleted messages\n        if ($config['delete_after_read']) {\n            imap_expunge($mailbox);\n        }\n    }\n    \n    imap_close($mailbox);\n    \/\/ echo \"Done!\\n\";\n    \n} catch (Exception $e) {\n    echo \"Error: \" . $e->getMessage() . \"\\n\";\n    exit(1);\n}\n?><\/pre>\n\n\n\n<p>Cron Job<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">*\t*\t*\t*\t*\t\/usr\/local\/bin\/php \/PATHTOMAIL\/mail\/read.php >> \/home\/taubmano\/rjt.org.uk\/mail\/email_reader.log 2>&amp;1<\/pre>\n\n\n\n<p><\/p>\n","protected":false},"template":"","class_list":["post-13994","home_assistant_tip","type-home_assistant_tip","status-publish","hentry","comments-off"],"_links":{"self":[{"href":"https:\/\/www.rjt.org.uk\/home\/wp-json\/wp\/v2\/home_assistant_tip\/13994","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rjt.org.uk\/home\/wp-json\/wp\/v2\/home_assistant_tip"}],"about":[{"href":"https:\/\/www.rjt.org.uk\/home\/wp-json\/wp\/v2\/types\/home_assistant_tip"}],"wp:attachment":[{"href":"https:\/\/www.rjt.org.uk\/home\/wp-json\/wp\/v2\/media?parent=13994"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}