diff --git a/homeassistant/components/imap/coordinator.py b/homeassistant/components/imap/coordinator.py index 72be5e9bcf0..59c24b11e51 100644 --- a/homeassistant/components/imap/coordinator.py +++ b/homeassistant/components/imap/coordinator.py @@ -110,6 +110,15 @@ class ImapMessage: header_base[key] += header_instances # type: ignore[assignment] return header_base + @property + def message_id(self) -> str | None: + """Get the message ID.""" + value: str + for header, value in self.email_message.items(): + if header == "Message-ID": + return value + return None + @property def date(self) -> datetime | None: """Get the date the email was sent.""" @@ -189,6 +198,7 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]): """Initiate imap client.""" self.imap_client = imap_client self.auth_errors: int = 0 + self._last_message_uid: str | None = None self._last_message_id: str | None = None self.custom_event_template = None _custom_event_template = entry.data.get(CONF_CUSTOM_EVENT_DATA_TEMPLATE) @@ -209,16 +219,22 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]): if self.imap_client is None: self.imap_client = await connect_to_server(self.config_entry.data) - async def _async_process_event(self, last_message_id: str) -> None: + async def _async_process_event(self, last_message_uid: str) -> None: """Send a event for the last message if the last message was changed.""" - response = await self.imap_client.fetch(last_message_id, "BODY.PEEK[]") + response = await self.imap_client.fetch(last_message_uid, "BODY.PEEK[]") if response.result == "OK": message = ImapMessage(response.lines[1]) + # Set `initial` to `False` if the last message is triggered again + initial: bool = True + if (message_id := message.message_id) == self._last_message_id: + initial = False + self._last_message_id = message_id data = { "server": self.config_entry.data[CONF_SERVER], "username": self.config_entry.data[CONF_USERNAME], "search": self.config_entry.data[CONF_SEARCH], "folder": self.config_entry.data[CONF_FOLDER], + "initial": initial, "date": message.date, "text": message.text, "sender": message.sender, @@ -231,18 +247,20 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]): data, parse_result=True ) _LOGGER.debug( - "imap custom template (%s) for msgid %s rendered to: %s", + "IMAP custom template (%s) for msguid %s (%s) rendered to: %s, initial: %s", self.custom_event_template, - last_message_id, + last_message_uid, + message_id, data["custom"], + initial, ) except TemplateError as err: data["custom"] = None _LOGGER.error( - "Error rendering imap custom template (%s) for msgid %s " + "Error rendering IMAP custom template (%s) for msguid %s " "failed with message: %s", self.custom_event_template, - last_message_id, + last_message_uid, err, ) data["text"] = message.text[ @@ -263,10 +281,12 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]): self.hass.bus.fire(EVENT_IMAP, data) _LOGGER.debug( - "Message with id %s processed, sender: %s, subject: %s", - last_message_id, + "Message with id %s (%s) processed, sender: %s, subject: %s, initial: %s", + last_message_uid, + message_id, message.sender, message.subject, + initial, ) async def _async_fetch_number_of_messages(self) -> int | None: @@ -282,20 +302,20 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]): f"Invalid response for search '{self.config_entry.data[CONF_SEARCH]}': {result} / {lines[0]}" ) if not (count := len(message_ids := lines[0].split())): - self._last_message_id = None + self._last_message_uid = None return 0 - last_message_id = ( + last_message_uid = ( str(message_ids[-1:][0], encoding=self.config_entry.data[CONF_CHARSET]) if count else None ) if ( count - and last_message_id is not None - and self._last_message_id != last_message_id + and last_message_uid is not None + and self._last_message_uid != last_message_uid ): - self._last_message_id = last_message_id - await self._async_process_event(last_message_id) + self._last_message_uid = last_message_uid + await self._async_process_event(last_message_uid) return count diff --git a/tests/components/imap/const.py b/tests/components/imap/const.py index e7fca106ff7..ec864fd4665 100644 --- a/tests/components/imap/const.py +++ b/tests/components/imap/const.py @@ -22,6 +22,7 @@ TEST_MESSAGE_HEADERS2 = ( b"To: notify@example.com\r\n" b"From: John Doe \r\n" b"Subject: Test subject\r\n" + b"Message-ID: " ) TEST_MESSAGE_HEADERS3 = b"" diff --git a/tests/components/imap/test_init.py b/tests/components/imap/test_init.py index b4ee11ba787..ceda841202c 100644 --- a/tests/components/imap/test_init.py +++ b/tests/components/imap/test_init.py @@ -512,6 +512,7 @@ async def test_reset_last_message( assert data["sender"] == "john.doe@example.com" assert data["subject"] == "Test subject" assert data["text"] + assert data["initial"] assert ( valid_date and isinstance(data["date"], datetime) @@ -628,7 +629,7 @@ async def test_message_is_truncated( [ ("{{ subject }}", "Test subject", None), ('{{ "@example.com" in sender }}', True, None), - ("{% bad template }}", None, "Error rendering imap custom template"), + ("{% bad template }}", None, "Error rendering IMAP custom template"), ], ids=["subject_test", "sender_filter", "template_error"], )