Last modified: 2025-07-23 22:13:09
< 2025-07-22 2025-07-24 >What is wrong with the AI Test User emails?
OK, it's because for some reason this is the body_text
rather than body_html
.
So simple_format
is not preventing XSS.
If I print <%= @email.body_text %>
it works correctly,
but <%= simple_format @email.body_text %>
or sanitize
or auto_link
all pass
through HTML tags that they shouldn't. Baffling, especially that sanitize
is not
sanitising.
OK, sanitize
is meant to strip dangerous HTML, not all HTML. So it is doing
the right thing. You want h
to escape all html.
So that solves the main problem, of being able to sneak through unescaped html by calling it
text/plain
. But why are the AI Test User emails not looking like html?
It's because it's not a multitype email! It's a single-part email where the Content-Type is
text/html
.
So I need to check the Content-Type of the email and display it accordingly. Good, done.
Next up: pipelining. I saw in the documentation at https://ruby-doc.org/3.3.0/gems/net-imap/Net/IMAP.html
that you can invoke multiple commands simultaneously on a single connection, by using Thread.start
.
Let's try that?
Doesn't work. Although you can do multiple commands simultaneously, the selected mailbox is a property of the session rather than the command. I may have to hack it to somehow do proper pipelining.
OK, let's make EmailContacts
now. So this is a table that maps an email to all of the contents that are
in it. So an EmailContact
has:
So if it is to me, it would have me with field="to"
, etc.
OK, so Rails has a cool thing for this, you make your EmailContact
model,
and then in Email
you say:
has_many :contacts, through: :email_contacts
And then for some Email object e
, you have e.contacts
which is all of the associated Contact
objects, and also e.email_contacts
which is the associated EmailContact
objects, which has
the metadata from the extra fields. Super cool.
Update: you need to also explicitly add:
has_many :email_contacts
And then when I parse an email how do I populate these tables? Done.
Trying to open a Stripe link I'm getting an error from Firefox about "cross-origin opener policies"??
Never heard of that before. It means I'm missing rel="noopener"
on the link. But I do have
rel="noopener"
on the link!
You need sandbox="allow-popups-to-escape-sandbox"
on the iframe
, fixed now.
Now I guess I want to delete the to
and from
fields on the Email model, and grab them from
EmailContacts
instead.
OK, seems to be working. One issue is that it now only shows one contact per email in the email view.
If I use where
instead of find_by
then I get all of them instead of just one.
It is now taking 700ms to render the index page, presumably because it has to do so many extra queries to grab out the contacts from different tables. How do I make it get them all in one go with a JOIN?
You can do @emails = current_user.emails.includes(:email_contacts)
, but it doesn't seem to be
any faster. Am I missing an index?
It's because email.to
uses email_contacts.where
which always hits the database. I should use
email_contacts.select
instead.
Now it is faster, but still has to look up the email address for every email contact, so
probably I want to add :contacts
to the includes()
list?
Yes, another improvement.
And finally it is also looking up attachments for every email, so let's include that as well.
Need to include :attachments_attachments
for that.
Now it's 100ms to render the index page. I think I want an index on the date field of emails
.
An index on (user_id,date)
is what I want, works well now, about 75ms, of which 55ms is in
"Views" and 10ms in "ActiveRecord". What are my Views doing slow? Or is that just normal?
Just normal according to ChatGPT.
I guess now I want to render the contacts on the email page using some sort of "component" that I can reuse across all the contacts in all the fields. It should emphasise their display name but leave the email address visible. Apparently "Rails Components" are a thing, or you can also render "partials".
Components gives you a way to add controller-like code to the component. I think a partial is enough for now.
Seems to work OK, I have managed to make it look awfully ugly though.
Auto-link doesn't put target="_blank" rel="noopener noreferer"
.
OK, let's look at the feature I'm doing all of this for: The Screener. Copied from hey.com.
When a new email comes in, if it's not from a contact we have "screened in", then the email goes into The Screener instead of the inbox. The main view will still only be the inbox. If there are unseen emails in the Screener then we'll have a label at the top saying so. If we click it then we go into The Screener, which shows us unseen emails from the last 7 days from non-screened-in contacts. At the bottom we can maybe "Load more" if we want to go back further, but by default they're not shown.
For each email in The Screener we can: view it, dismiss it (mark seen), or screen the person in.
We maybe kind of want the mailbox view to be a "component" that takes a filter and sort order and shows us emails according to those.
One issue is what if an email has multiple "From" values? Consult ChatGPT on that.
What is the simplest way to start?
Good, is working.
Emails should be marked as seen as soon as they are viewed, unfortunately we can't do that in the GET controller because Hotwire prefetches the page. ChatGPT suggests using a "Stimulus Controller" to POST an update to mark it as seen.
I have done what it suggested, passing in the URL to fetch with data-mark-seen-url-value
and grabbing
it out with this.data.get("url")
, but it is coming out as null, hmm.
OK, it is just totally different, working now, I consulted https://stimulus.hotwired.dev/reference/values
By having mark_seen
and mark_unseen
return "204 No Content", they don't replace the page view with
anything. However we do need the "Mark as unseen" button text to change to "Mark as seen" once it succeeds.
m.media-amazon.com
on html emails